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