1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 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 
 45      raise NotImplementedError("You need to override this function") 
  46   
 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   
 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   
 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   
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   
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   
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   
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           
214           
215          if resp.status < 300: 
216              if resp.status == 204: 
217                   
218                   
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   
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   
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   
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   
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   
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 
 290   
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   
306   
307      @property 
 310   
330   
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   
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   
356          return body_value.SerializeToString() 
 357   
359          return self._protocol_buffer.FromString(content) 
 360   
361      @property 
363          return self._protocol_buffer() 
  364   
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               
392              patch[key] = None 
393          elif original_value != modified_value: 
394              if type(original_value) == type({}): 
395                   
396                  patch[key] = makepatch(original_value, modified_value) 
397              else: 
398                   
399                  patch[key] = modified_value 
400          else: 
401               
402              pass 
403      for key in modified: 
404          if key not in original: 
405              patch[key] = modified[key] 
406   
407      return patch 
 408