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

Source Code for Module googleapiclient.model

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