|
1 #!/usr/bin/env python |
|
2 # |
|
3 # Copyright 2007 Google Inc. |
|
4 # |
|
5 # Licensed under the Apache License, Version 2.0 (the "License"); |
|
6 # you may not use this file except in compliance with the License. |
|
7 # You may obtain a copy of the License at |
|
8 # |
|
9 # http://www.apache.org/licenses/LICENSE-2.0 |
|
10 # |
|
11 # Unless required by applicable law or agreed to in writing, software |
|
12 # distributed under the License is distributed on an "AS IS" BASIS, |
|
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
14 # See the License for the specific language governing permissions and |
|
15 # limitations under the License. |
|
16 # |
|
17 |
|
18 """Makes API calls to various Google-provided services. |
|
19 |
|
20 Provides methods for making calls into Google Apphosting services and APIs |
|
21 from your application code. This code will only work properly from within |
|
22 the Google Apphosting environment. |
|
23 """ |
|
24 |
|
25 |
|
26 import sys |
|
27 from google.net.proto import ProtocolBuffer |
|
28 from google.appengine import runtime |
|
29 from google.appengine.runtime import apiproxy_errors |
|
30 from google3.apphosting.runtime import _apphosting_runtime___python__apiproxy |
|
31 |
|
32 OK = 0 |
|
33 RPC_FAILED = 1 |
|
34 CALL_NOT_FOUND = 2 |
|
35 ARGUMENT_ERROR = 3 |
|
36 DEADLINE_EXCEEDED = 4 |
|
37 CANCELLED = 5 |
|
38 APPLICATION_ERROR = 6 |
|
39 OTHER_ERROR = 7 |
|
40 OVER_QUOTA = 8 |
|
41 REQUEST_TOO_LARGE = 9 |
|
42 CAPABILITY_DISABLED = 10 |
|
43 |
|
44 _ExceptionsMap = { |
|
45 RPC_FAILED: |
|
46 (apiproxy_errors.RPCFailedError, |
|
47 "The remote RPC to the application server failed for the call %s.%s()."), |
|
48 CALL_NOT_FOUND: |
|
49 (apiproxy_errors.CallNotFoundError, |
|
50 "The API package '%s' or call '%s()' was not found."), |
|
51 ARGUMENT_ERROR: |
|
52 (apiproxy_errors.ArgumentError, |
|
53 "An error occurred parsing (locally or remotely) the arguments to %s.%s()."), |
|
54 DEADLINE_EXCEEDED: |
|
55 (apiproxy_errors.DeadlineExceededError, |
|
56 "The API call %s.%s() took too long to respond and was cancelled."), |
|
57 CANCELLED: |
|
58 (apiproxy_errors.CancelledError, |
|
59 "The API call %s.%s() was explicitly cancelled."), |
|
60 OTHER_ERROR: |
|
61 (apiproxy_errors.Error, |
|
62 "An error occurred for the API request %s.%s()."), |
|
63 OVER_QUOTA: |
|
64 (apiproxy_errors.OverQuotaError, |
|
65 "The API call %s.%s() required more quota than is available."), |
|
66 REQUEST_TOO_LARGE: |
|
67 (apiproxy_errors.RequestTooLargeError, |
|
68 "The request to API call %s.%s() was too large."), |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 } |
|
76 |
|
77 class RPC(object): |
|
78 """A RPC object, suitable for talking to remote services. |
|
79 |
|
80 Each instance of this object can be used only once, and should not be reused. |
|
81 |
|
82 Stores the data members and methods for making RPC calls via the APIProxy. |
|
83 """ |
|
84 |
|
85 IDLE = 0 |
|
86 RUNNING = 1 |
|
87 FINISHING = 2 |
|
88 |
|
89 def __init__(self, package=None, call=None, request=None, response=None, |
|
90 callback=None): |
|
91 """Constructor for the RPC object. All arguments are optional, and |
|
92 simply set members on the class. These data members will be |
|
93 overriden by values passed to MakeCall. |
|
94 |
|
95 Args: |
|
96 package: string, the package for the call |
|
97 call: string, the call within the package |
|
98 request: ProtocolMessage instance, appropriate for the arguments |
|
99 response: ProtocolMessage instance, appropriate for the response |
|
100 callback: callable, called when call is complete |
|
101 """ |
|
102 self.__exception = None |
|
103 self.__traceback = None |
|
104 self.__result_dict = {} |
|
105 self.__state = RPC.IDLE |
|
106 |
|
107 self.package = package |
|
108 self.call = call |
|
109 self.request = request |
|
110 self.response = response |
|
111 self.callback = callback |
|
112 |
|
113 |
|
114 def MakeCall(self, package=None, call=None, request=None, response=None, |
|
115 callback=None): |
|
116 """Makes an asynchronous (i.e. non-blocking) API call within the |
|
117 specified package for the specified call method. request and response must |
|
118 be the appropriately typed ProtocolBuffers for the API call. |
|
119 callback, if provided, will be called when the request completes |
|
120 successfully or when an error occurs. If an error has ocurred, the |
|
121 exception() method on this class will return the error, which can be |
|
122 accessed from the callback. |
|
123 |
|
124 Args: |
|
125 Same as constructor; see __init__. |
|
126 |
|
127 Raises: |
|
128 TypeError or AssertionError if an argument is of an invalid type. |
|
129 AssertionError or RuntimeError is an RPC is already in use. |
|
130 """ |
|
131 self.callback = callback or self.callback |
|
132 self.package = package or self.package |
|
133 self.call = call or self.call |
|
134 self.request = request or self.request |
|
135 self.response = response or self.response |
|
136 |
|
137 assert self.__state is RPC.IDLE, ("RPC for %s.%s has already been started" % |
|
138 (self.package, self.call)) |
|
139 assert self.callback is None or callable(self.callback) |
|
140 assert isinstance(self.request, ProtocolBuffer.ProtocolMessage) |
|
141 assert isinstance(self.response, ProtocolBuffer.ProtocolMessage) |
|
142 |
|
143 e = ProtocolBuffer.Encoder() |
|
144 self.request.Output(e) |
|
145 |
|
146 self.__state = RPC.RUNNING |
|
147 _apphosting_runtime___python__apiproxy.MakeCall( |
|
148 self.package, self.call, e.buffer(), self.__result_dict, |
|
149 self.__MakeCallDone, self) |
|
150 |
|
151 def Wait(self): |
|
152 """Waits on the API call associated with this RPC. The callback, |
|
153 if provided, will be executed before Wait() returns. If this RPC |
|
154 is already complete, or if the RPC was never started, this |
|
155 function will return immediately. |
|
156 |
|
157 Raises: |
|
158 InterruptedError if a callback throws an uncaught exception. |
|
159 """ |
|
160 try: |
|
161 rpc_completed = _apphosting_runtime___python__apiproxy.Wait(self) |
|
162 except runtime.DeadlineExceededError: |
|
163 raise |
|
164 except apiproxy_errors.InterruptedError: |
|
165 raise |
|
166 except: |
|
167 exc_class, exc, tb = sys.exc_info() |
|
168 if (isinstance(exc, SystemError) and |
|
169 exc.args == ('uncaught RPC exception',)): |
|
170 raise |
|
171 rpc = None |
|
172 if hasattr(exc, "_appengine_apiproxy_rpc"): |
|
173 rpc = exc._appengine_apiproxy_rpc |
|
174 new_exc = apiproxy_errors.InterruptedError(exc, rpc) |
|
175 raise new_exc.__class__, new_exc, tb |
|
176 |
|
177 assert rpc_completed, ("RPC for %s.%s was not completed, and no other " + |
|
178 "exception was raised " % (self.package, self.call)) |
|
179 |
|
180 def CheckSuccess(self): |
|
181 """If there was an exception, raise it now. |
|
182 |
|
183 Raises: |
|
184 Exception of the API call or the callback, if any. |
|
185 """ |
|
186 if self.exception and self.__traceback: |
|
187 raise self.exception.__class__, self.exception, self.__traceback |
|
188 if self.exception: |
|
189 raise self.exception |
|
190 |
|
191 @property |
|
192 def exception(self): |
|
193 return self.__exception |
|
194 |
|
195 @property |
|
196 def state(self): |
|
197 return self.__state |
|
198 |
|
199 def __MakeCallDone(self): |
|
200 self.__state = RPC.FINISHING |
|
201 if self.__result_dict['error'] == APPLICATION_ERROR: |
|
202 self.__exception = apiproxy_errors.ApplicationError( |
|
203 self.__result_dict['application_error'], |
|
204 self.__result_dict['error_detail']) |
|
205 elif self.__result_dict['error'] == CAPABILITY_DISABLED: |
|
206 if self.__result_dict['error_detail']: |
|
207 self.__exception = apiproxy_errors.CapabilityDisabledError( |
|
208 self.__result_dict['error_detail']) |
|
209 else: |
|
210 self.__exception = apiproxy_errors.CapabilityDisabledError( |
|
211 "The API call %s.%s() is temporarily unavailable." % ( |
|
212 self.package, self.call)) |
|
213 elif _ExceptionsMap.has_key(self.__result_dict['error']): |
|
214 exception_entry = _ExceptionsMap[self.__result_dict['error']] |
|
215 self.__exception = exception_entry[0]( |
|
216 exception_entry[1] % (self.package, self.call)) |
|
217 else: |
|
218 try: |
|
219 self.response.ParseFromString(self.__result_dict['result_string']) |
|
220 except Exception, e: |
|
221 self.__exception = e |
|
222 if self.callback: |
|
223 try: |
|
224 self.callback() |
|
225 except: |
|
226 exc_class, self.__exception, self.__traceback = sys.exc_info() |
|
227 self.__exception._appengine_apiproxy_rpc = self |
|
228 raise |
|
229 |
|
230 |
|
231 def MakeSyncCall(package, call, request, response): |
|
232 """Makes a synchronous (i.e. blocking) API call within the specified |
|
233 package for the specified call method. request and response must be the |
|
234 appropriately typed ProtocolBuffers for the API call. An exception is |
|
235 thrown if an error occurs when communicating with the system. |
|
236 |
|
237 Args: |
|
238 See MakeCall above. |
|
239 |
|
240 Raises: |
|
241 See CheckSuccess() above. |
|
242 """ |
|
243 rpc = RPC() |
|
244 rpc.MakeCall(package, call, request, response) |
|
245 rpc.Wait() |
|
246 rpc.CheckSuccess() |