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