blob: 9781b35ff1377d83db08087e1eaa58d2a4a61b7e [file] [log] [blame]
Joe Gregorio3ad5e9a2010-12-09 15:01:04 -05001#!/usr/bin/python2.4
2#
3# Copyright 2010 Google Inc. All Rights Reserved.
4
Joe Gregorioec343652011-02-16 16:52:51 -05005"""Model objects for requests and responses.
Joe Gregorio3ad5e9a2010-12-09 15:01:04 -05006
7Each API may support one or more serializations, such
8as JSON, Atom, etc. The model classes are responsible
9for converting between the wire format and the Python
10object representation.
11"""
12
13__author__ = 'jcgregorio@google.com (Joe Gregorio)'
14
15import logging
16import urllib
17
Joe Gregoriob843fa22010-12-13 16:26:07 -050018from anyjson import simplejson
19from errors import HttpError
Joe Gregorio3ad5e9a2010-12-09 15:01:04 -050020
Joe Gregoriodeeb0202011-02-15 14:49:57 -050021
Joe Gregorioabda96f2011-02-11 20:19:33 -050022def _abstract():
23 raise NotImplementedError('You need to override this function')
Joe Gregorio3ad5e9a2010-12-09 15:01:04 -050024
Joe Gregorioabda96f2011-02-11 20:19:33 -050025
26class Model(object):
27 """Model base class.
28
29 All Model classes should implement this interface.
30 The Model serializes and de-serializes between a wire
31 format such as JSON and a Python object representation.
32 """
33
34 def request(self, headers, path_params, query_params, body_value):
35 """Updates outgoing requests with a deserialized body.
36
37 Args:
38 headers: dict, request headers
39 path_params: dict, parameters that appear in the request path
40 query_params: dict, parameters that appear in the query
41 body_value: object, the request body as a Python object, which must be
42 serializable.
43 Returns:
44 A tuple of (headers, path_params, query, body)
45
46 headers: dict, request headers
47 path_params: dict, parameters that appear in the request path
48 query: string, query part of the request URI
49 body: string, the body serialized in the desired wire format.
50 """
51 _abstract()
52
53 def response(self, resp, content):
54 """Convert the response wire format into a Python object.
55
56 Args:
57 resp: httplib2.Response, the HTTP response headers and status
58 content: string, the body of the HTTP response
59
60 Returns:
61 The body de-serialized as a Python object.
62
63 Raises:
64 apiclient.errors.HttpError if a non 2xx response is received.
65 """
66 _abstract()
67
68
69class JsonModel(Model):
Joe Gregorio3ad5e9a2010-12-09 15:01:04 -050070 """Model class for JSON.
71
72 Serializes and de-serializes between JSON and the Python
73 object representation of HTTP request and response bodies.
74 """
75
Joe Gregorio10894442011-02-23 14:46:55 -050076 def __init__(self, data_wrapper=False):
Joe Gregoriod433b2a2011-02-22 10:51:51 -050077 """Construct a JsonModel
78
79 Args:
80 data_wrapper: boolean, wrap requests and responses in a data wrapper
81 """
82 self._data_wrapper = data_wrapper
83
Joe Gregorio3ad5e9a2010-12-09 15:01:04 -050084 def request(self, headers, path_params, query_params, body_value):
85 """Updates outgoing requests with JSON bodies.
86
87 Args:
88 headers: dict, request headers
89 path_params: dict, parameters that appear in the request path
90 query_params: dict, parameters that appear in the query
91 body_value: object, the request body as a Python object, which must be
92 serializable by simplejson.
93 Returns:
94 A tuple of (headers, path_params, query, body)
95
96 headers: dict, request headers
97 path_params: dict, parameters that appear in the request path
98 query: string, query part of the request URI
99 body: string, the body serialized as JSON
100 """
101 query = self._build_query(query_params)
102 headers['accept'] = 'application/json'
103 if 'user-agent' in headers:
104 headers['user-agent'] += ' '
105 else:
106 headers['user-agent'] = ''
107 headers['user-agent'] += 'google-api-python-client/1.0'
Joe Gregoriod433b2a2011-02-22 10:51:51 -0500108
109 if (isinstance(body_value, dict) and 'data' not in body_value and
110 self._data_wrapper):
111 body_value = {'data': body_value}
Joe Gregorio3ad5e9a2010-12-09 15:01:04 -0500112 if body_value is None:
113 return (headers, path_params, query, None)
114 else:
115 headers['content-type'] = 'application/json'
116 return (headers, path_params, query, simplejson.dumps(body_value))
117
118 def _build_query(self, params):
119 """Builds a query string.
120
121 Args:
122 params: dict, the query parameters
123
124 Returns:
125 The query parameters properly encoded into an HTTP URI query string.
126 """
127 params.update({'alt': 'json'})
128 astuples = []
129 for key, value in params.iteritems():
Joe Gregorio61d7e962011-02-22 22:52:07 -0500130 if type(value) == type([]):
131 for x in value:
132 x = x.encode('utf-8')
133 astuples.append((key, x))
134 else:
135 if getattr(value, 'encode', False) and callable(value.encode):
136 value = value.encode('utf-8')
137 astuples.append((key, value))
Joe Gregorio3ad5e9a2010-12-09 15:01:04 -0500138 return '?' + urllib.urlencode(astuples)
139
140 def response(self, resp, content):
141 """Convert the response wire format into a Python object.
142
143 Args:
144 resp: httplib2.Response, the HTTP response headers and status
145 content: string, the body of the HTTP response
146
147 Returns:
148 The body de-serialized as a Python object.
149
150 Raises:
151 apiclient.errors.HttpError if a non 2xx response is received.
152 """
153 # Error handling is TBD, for example, do we retry
154 # for some operation/error combinations?
155 if resp.status < 300:
156 if resp.status == 204:
157 # A 204: No Content response should be treated differently
158 # to all the other success states
159 return simplejson.loads('{}')
160 body = simplejson.loads(content)
161 if isinstance(body, dict) and 'data' in body:
162 body = body['data']
163 return body
164 else:
165 logging.debug('Content from bad request was: %s' % content)
Ali Afshar2dcc6522010-12-16 10:11:53 +0100166 raise HttpError(resp, content)