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   
 30  from six.moves.urllib.parse import urlencode 
 31   
 32  from googleapiclient import __version__ 
 33  from googleapiclient.errors import HttpError 
 34   
 35   
 36  LOGGER = logging.getLogger(__name__) 
 37   
 38  dump_request_response = False 
 42    raise NotImplementedError('You need to override this function') 
  43   
 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   
 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        googleapiclient.errors.HttpError if a non 2xx response is received. 
 84      """ 
 85      _abstract() 
   86   
 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   
108      """Logs debugging information about the request if requested.""" 
109      if dump_request_response: 
110        LOGGER.info('--request-start--') 
111        LOGGER.info('-headers-start-') 
112        for h, v in six.iteritems(headers): 
113          LOGGER.info('%s: %s', h, v) 
114        LOGGER.info('-headers-end-') 
115        LOGGER.info('-path-parameters-start-') 
116        for h, v in six.iteritems(path_params): 
117          LOGGER.info('%s: %s', h, v) 
118        LOGGER.info('-path-parameters-end-') 
119        LOGGER.info('body: %s', body) 
120        LOGGER.info('query: %s', query) 
121        LOGGER.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 json. 
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/%s (gzip)' % __version__ 
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   
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 six.iteritems(params): 
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 isinstance(value, six.text_type) and callable(value.encode): 
174            value = value.encode('utf-8') 
175          astuples.append((key, value)) 
176      return '?' + urlencode(astuples) 
 177   
179      """Logs debugging information about the response if requested.""" 
180      if dump_request_response: 
181        LOGGER.info('--response-start--') 
182        for h, v in six.iteritems(resp): 
183          LOGGER.info('%s: %s', h, v) 
184        if content: 
185          LOGGER.info(content) 
186        LOGGER.info('--response-end--') 
 187   
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        googleapiclient.errors.HttpError if a non 2xx response is received. 
200      """ 
201      self._log_response(resp, content) 
202       
203       
204      if resp.status < 300: 
205        if resp.status == 204: 
206           
207           
208          return self.no_content_response 
209        return self.deserialize(content) 
210      else: 
211        LOGGER.debug('Content from bad request was: %s' % content) 
212        raise HttpError(resp, content) 
 213   
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   
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   
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   
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 json.dumps(body_value) 
 261   
263      try: 
264          content = content.decode('utf-8') 
265      except AttributeError: 
266          pass 
267      body = json.loads(content) 
268      if self._data_wrapper and isinstance(body, dict) and 'data' in body: 
269        body = body['data'] 
270      return body 
 271   
272    @property 
 275   
278    """Model class for requests that don't return JSON. 
279   
280    Serializes and de-serializes between JSON and the Python 
281    object representation of HTTP request, and returns the raw bytes 
282    of the response body. 
283    """ 
284    accept = '*/*' 
285    content_type = 'application/json' 
286    alt_param = None 
287   
290   
291    @property 
 294   
313   
316    """Model class for protocol buffers. 
317   
318    Serializes and de-serializes the binary protocol buffer sent in the HTTP 
319    request and response bodies. 
320    """ 
321    accept = 'application/x-protobuf' 
322    content_type = 'application/x-protobuf' 
323    alt_param = 'proto' 
324   
326      """Constructs a ProtocolBufferModel. 
327   
328      The serialzed protocol buffer returned in an HTTP response will be 
329      de-serialized using the given protocol buffer class. 
330   
331      Args: 
332        protocol_buffer: The protocol buffer class used to de-serialize a 
333        response from the API. 
334      """ 
335      self._protocol_buffer = protocol_buffer 
 336   
338      return body_value.SerializeToString() 
 339   
341      return self._protocol_buffer.FromString(content) 
 342   
343    @property 
345      return self._protocol_buffer() 
  346   
349    """Create a patch object. 
350   
351    Some methods support PATCH, an efficient way to send updates to a resource. 
352    This method allows the easy construction of patch bodies by looking at the 
353    differences between a resource before and after it was modified. 
354   
355    Args: 
356      original: object, the original deserialized resource 
357      modified: object, the modified deserialized resource 
358    Returns: 
359      An object that contains only the changes from original to modified, in a 
360      form suitable to pass to a PATCH method. 
361   
362    Example usage: 
363      item = service.activities().get(postid=postid, userid=userid).execute() 
364      original = copy.deepcopy(item) 
365      item['object']['content'] = 'This is updated.' 
366      service.activities.patch(postid=postid, userid=userid, 
367        body=makepatch(original, item)).execute() 
368    """ 
369    patch = {} 
370    for key, original_value in six.iteritems(original): 
371      modified_value = modified.get(key, None) 
372      if modified_value is None: 
373         
374        patch[key] = None 
375      elif original_value != modified_value: 
376        if type(original_value) == type({}): 
377           
378          patch[key] = makepatch(original_value, modified_value) 
379        else: 
380           
381          patch[key] = modified_value 
382      else: 
383         
384        pass 
385    for key in modified: 
386      if key not in original: 
387        patch[key] = modified[key] 
388   
389    return patch 
 390