googleapiclient.model

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