Package apiclient :: Module model
[hide private]
[frames] | no frames]

Source Code for Module apiclient.model

  1  #!/usr/bin/python2.4 
  2  # 
  3  # Copyright (C) 2010 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  """Model objects for requests and responses. 
 18   
 19  Each API may support one or more serializations, such 
 20  as JSON, Atom, etc. The model classes are responsible 
 21  for converting between the wire format and the Python 
 22  object representation. 
 23  """ 
 24   
 25  __author__ = 'jcgregorio@google.com (Joe Gregorio)' 
 26   
 27  import gflags 
 28  import logging 
 29  import urllib 
 30   
 31  from errors import HttpError 
 32  from oauth2client.anyjson import simplejson 
 33   
 34  FLAGS = gflags.FLAGS 
 35   
 36  gflags.DEFINE_boolean('dump_request_response', False, 
 37                        'Dump all http server requests and responses. ' 
 38                       ) 
39 40 41 -def _abstract():
42 raise NotImplementedError('You need to override this function')
43
44 45 -class Model(object):
46 """Model base class. 47 48 All Model classes should implement this interface. 49 The Model serializes and de-serializes between a wire 50 format such as JSON and a Python object representation. 51 """ 52
53 - def request(self, headers, path_params, query_params, body_value):
54 """Updates outgoing requests with a serialized body. 55 56 Args: 57 headers: dict, request headers 58 path_params: dict, parameters that appear in the request path 59 query_params: dict, parameters that appear in the query 60 body_value: object, the request body as a Python object, which must be 61 serializable. 62 Returns: 63 A tuple of (headers, path_params, query, body) 64 65 headers: dict, request headers 66 path_params: dict, parameters that appear in the request path 67 query: string, query part of the request URI 68 body: string, the body serialized in the desired wire format. 69 """ 70 _abstract()
71
72 - def response(self, resp, content):
73 """Convert the response wire format into a Python object. 74 75 Args: 76 resp: httplib2.Response, the HTTP response headers and status 77 content: string, the body of the HTTP response 78 79 Returns: 80 The body de-serialized as a Python object. 81 82 Raises: 83 apiclient.errors.HttpError if a non 2xx response is received. 84 """ 85 _abstract()
86
87 88 -class BaseModel(Model):
89 """Base model class. 90 91 Subclasses should provide implementations for the "serialize" and 92 "deserialize" methods, as well as values for the following class attributes. 93 94 Attributes: 95 accept: The value to use for the HTTP Accept header. 96 content_type: The value to use for the HTTP Content-type header. 97 no_content_response: The value to return when deserializing a 204 "No 98 Content" response. 99 alt_param: The value to supply as the "alt" query parameter for requests. 100 """ 101 102 accept = None 103 content_type = None 104 no_content_response = None 105 alt_param = None 106
107 - def _log_request(self, headers, path_params, query, body):
108 """Logs debugging information about the request if requested.""" 109 if FLAGS.dump_request_response: 110 logging.info('--request-start--') 111 logging.info('-headers-start-') 112 for h, v in headers.iteritems(): 113 logging.info('%s: %s', h, v) 114 logging.info('-headers-end-') 115 logging.info('-path-parameters-start-') 116 for h, v in path_params.iteritems(): 117 logging.info('%s: %s', h, v) 118 logging.info('-path-parameters-end-') 119 logging.info('body: %s', body) 120 logging.info('query: %s', query) 121 logging.info('--request-end--')
122
123 - def request(self, headers, path_params, query_params, body_value):
124 """Updates outgoing requests with a serialized body. 125 126 Args: 127 headers: dict, request headers 128 path_params: dict, parameters that appear in the request path 129 query_params: dict, parameters that appear in the query 130 body_value: object, the request body as a Python object, which must be 131 serializable by simplejson. 132 Returns: 133 A tuple of (headers, path_params, query, body) 134 135 headers: dict, request headers 136 path_params: dict, parameters that appear in the request path 137 query: string, query part of the request URI 138 body: string, the body serialized as JSON 139 """ 140 query = self._build_query(query_params) 141 headers['accept'] = self.accept 142 headers['accept-encoding'] = 'gzip, deflate' 143 if 'user-agent' in headers: 144 headers['user-agent'] += ' ' 145 else: 146 headers['user-agent'] = '' 147 headers['user-agent'] += 'google-api-python-client/1.0' 148 149 if body_value is not None: 150 headers['content-type'] = self.content_type 151 body_value = self.serialize(body_value) 152 self._log_request(headers, path_params, query, body_value) 153 return (headers, path_params, query, body_value)
154
155 - def _build_query(self, params):
156 """Builds a query string. 157 158 Args: 159 params: dict, the query parameters 160 161 Returns: 162 The query parameters properly encoded into an HTTP URI query string. 163 """ 164 if self.alt_param is not None: 165 params.update({'alt': self.alt_param}) 166 astuples = [] 167 for key, value in params.iteritems(): 168 if type(value) == type([]): 169 for x in value: 170 x = x.encode('utf-8') 171 astuples.append((key, x)) 172 else: 173 if getattr(value, 'encode', False) and callable(value.encode): 174 value = value.encode('utf-8') 175 astuples.append((key, value)) 176 return '?' + urllib.urlencode(astuples)
177
178 - def _log_response(self, resp, content):
179 """Logs debugging information about the response if requested.""" 180 if FLAGS.dump_request_response: 181 logging.info('--response-start--') 182 for h, v in resp.iteritems(): 183 logging.info('%s: %s', h, v) 184 if content: 185 logging.info(content) 186 logging.info('--response-end--')
187
188 - def response(self, resp, content):
189 """Convert the response wire format into a Python object. 190 191 Args: 192 resp: httplib2.Response, the HTTP response headers and status 193 content: string, the body of the HTTP response 194 195 Returns: 196 The body de-serialized as a Python object. 197 198 Raises: 199 apiclient.errors.HttpError if a non 2xx response is received. 200 """ 201 self._log_response(resp, content) 202 # Error handling is TBD, for example, do we retry 203 # for some operation/error combinations? 204 if resp.status < 300: 205 if resp.status == 204: 206 # A 204: No Content response should be treated differently 207 # to all the other success states 208 return self.no_content_response 209 return self.deserialize(content) 210 else: 211 logging.debug('Content from bad request was: %s' % content) 212 raise HttpError(resp, content)
213
214 - def serialize(self, body_value):
215 """Perform the actual Python object serialization. 216 217 Args: 218 body_value: object, the request body as a Python object. 219 220 Returns: 221 string, the body in serialized form. 222 """ 223 _abstract()
224
225 - def deserialize(self, content):
226 """Perform the actual deserialization from response string to Python 227 object. 228 229 Args: 230 content: string, the body of the HTTP response 231 232 Returns: 233 The body de-serialized as a Python object. 234 """ 235 _abstract()
236
237 238 -class JsonModel(BaseModel):
239 """Model class for JSON. 240 241 Serializes and de-serializes between JSON and the Python 242 object representation of HTTP request and response bodies. 243 """ 244 accept = 'application/json' 245 content_type = 'application/json' 246 alt_param = 'json' 247
248 - def __init__(self, data_wrapper=False):
249 """Construct a JsonModel. 250 251 Args: 252 data_wrapper: boolean, wrap requests and responses in a data wrapper 253 """ 254 self._data_wrapper = data_wrapper
255
256 - def serialize(self, body_value):
257 if (isinstance(body_value, dict) and 'data' not in body_value and 258 self._data_wrapper): 259 body_value = {'data': body_value} 260 return simplejson.dumps(body_value)
261
262 - def deserialize(self, content):
263 body = simplejson.loads(content) 264 if isinstance(body, dict) and 'data' in body: 265 body = body['data'] 266 return body
267 268 @property
269 - def no_content_response(self):
270 return {}
271
272 273 -class RawModel(JsonModel):
274 """Model class for requests that don't return JSON. 275 276 Serializes and de-serializes between JSON and the Python 277 object representation of HTTP request, and returns the raw bytes 278 of the response body. 279 """ 280 accept = '*/*' 281 content_type = 'application/json' 282 alt_param = None 283
284 - def deserialize(self, content):
285 return content
286 287 @property
288 - def no_content_response(self):
289 return ''
290
291 292 -class MediaModel(JsonModel):
293 """Model class for requests that return Media. 294 295 Serializes and de-serializes between JSON and the Python 296 object representation of HTTP request, and returns the raw bytes 297 of the response body. 298 """ 299 accept = '*/*' 300 content_type = 'application/json' 301 alt_param = 'media' 302
303 - def deserialize(self, content):
304 return content
305 306 @property
307 - def no_content_response(self):
308 return ''
309
310 311 -class ProtocolBufferModel(BaseModel):
312 """Model class for protocol buffers. 313 314 Serializes and de-serializes the binary protocol buffer sent in the HTTP 315 request and response bodies. 316 """ 317 accept = 'application/x-protobuf' 318 content_type = 'application/x-protobuf' 319 alt_param = 'proto' 320
321 - def __init__(self, protocol_buffer):
322 """Constructs a ProtocolBufferModel. 323 324 The serialzed protocol buffer returned in an HTTP response will be 325 de-serialized using the given protocol buffer class. 326 327 Args: 328 protocol_buffer: The protocol buffer class used to de-serialize a 329 response from the API. 330 """ 331 self._protocol_buffer = protocol_buffer
332
333 - def serialize(self, body_value):
334 return body_value.SerializeToString()
335
336 - def deserialize(self, content):
337 return self._protocol_buffer.FromString(content)
338 339 @property
340 - def no_content_response(self):
341 return self._protocol_buffer()
342
343 344 -def makepatch(original, modified):
345 """Create a patch object. 346 347 Some methods support PATCH, an efficient way to send updates to a resource. 348 This method allows the easy construction of patch bodies by looking at the 349 differences between a resource before and after it was modified. 350 351 Args: 352 original: object, the original deserialized resource 353 modified: object, the modified deserialized resource 354 Returns: 355 An object that contains only the changes from original to modified, in a 356 form suitable to pass to a PATCH method. 357 358 Example usage: 359 item = service.activities().get(postid=postid, userid=userid).execute() 360 original = copy.deepcopy(item) 361 item['object']['content'] = 'This is updated.' 362 service.activities.patch(postid=postid, userid=userid, 363 body=makepatch(original, item)).execute() 364 """ 365 patch = {} 366 for key, original_value in original.iteritems(): 367 modified_value = modified.get(key, None) 368 if modified_value is None: 369 # Use None to signal that the element is deleted 370 patch[key] = None 371 elif original_value != modified_value: 372 if type(original_value) == type({}): 373 # Recursively descend objects 374 patch[key] = makepatch(original_value, modified_value) 375 else: 376 # In the case of simple types or arrays we just replace 377 patch[key] = modified_value 378 else: 379 # Don't add anything to patch if there's no change 380 pass 381 for key in modified: 382 if key not in original: 383 patch[key] = modified[key] 384 385 return patch
386