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