1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """Model objects for requests and responses.
18
19 Each API may support one or more serializations, such
20 as JSON, Atom, etc. The model classes are responsible
21 for converting between the wire format and the Python
22 object representation.
23 """
24
25 __author__ = 'jcgregorio@google.com (Joe Gregorio)'
26
27 import gflags
28 import logging
29 import urllib
30
31 from errors import HttpError
32 from oauth2client.anyjson import simplejson
33
34 FLAGS = gflags.FLAGS
35
36 gflags.DEFINE_boolean('dump_request_response', False,
37 'Dump all http server requests and responses. '
38 )
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 apiclient.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 FLAGS.dump_request_response:
110 logging.info('--request-start--')
111 logging.info('-headers-start-')
112 for h, v in headers.iteritems():
113 logging.info('%s: %s', h, v)
114 logging.info('-headers-end-')
115 logging.info('-path-parameters-start-')
116 for h, v in path_params.iteritems():
117 logging.info('%s: %s', h, v)
118 logging.info('-path-parameters-end-')
119 logging.info('body: %s', body)
120 logging.info('query: %s', query)
121 logging.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 simplejson.
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/1.0'
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 params.iteritems():
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 getattr(value, 'encode', False) and callable(value.encode):
174 value = value.encode('utf-8')
175 astuples.append((key, value))
176 return '?' + urllib.urlencode(astuples)
177
179 """Logs debugging information about the response if requested."""
180 if FLAGS.dump_request_response:
181 logging.info('--response-start--')
182 for h, v in resp.iteritems():
183 logging.info('%s: %s', h, v)
184 if content:
185 logging.info(content)
186 logging.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 apiclient.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 logging.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 simplejson.dumps(body_value)
261
263 body = simplejson.loads(content)
264 if isinstance(body, dict) and 'data' in body:
265 body = body['data']
266 return body
267
268 @property
271
274 """Model class for requests that don't return JSON.
275
276 Serializes and de-serializes between JSON and the Python
277 object representation of HTTP request, and returns the raw bytes
278 of the response body.
279 """
280 accept = '*/*'
281 content_type = 'application/json'
282 alt_param = None
283
286
287 @property
290
309
312 """Model class for protocol buffers.
313
314 Serializes and de-serializes the binary protocol buffer sent in the HTTP
315 request and response bodies.
316 """
317 accept = 'application/x-protobuf'
318 content_type = 'application/x-protobuf'
319 alt_param = 'proto'
320
322 """Constructs a ProtocolBufferModel.
323
324 The serialzed protocol buffer returned in an HTTP response will be
325 de-serialized using the given protocol buffer class.
326
327 Args:
328 protocol_buffer: The protocol buffer class used to de-serialize a
329 response from the API.
330 """
331 self._protocol_buffer = protocol_buffer
332
334 return body_value.SerializeToString()
335
337 return self._protocol_buffer.FromString(content)
338
339 @property
341 return self._protocol_buffer()
342
345 """Create a patch object.
346
347 Some methods support PATCH, an efficient way to send updates to a resource.
348 This method allows the easy construction of patch bodies by looking at the
349 differences between a resource before and after it was modified.
350
351 Args:
352 original: object, the original deserialized resource
353 modified: object, the modified deserialized resource
354 Returns:
355 An object that contains only the changes from original to modified, in a
356 form suitable to pass to a PATCH method.
357
358 Example usage:
359 item = service.activities().get(postid=postid, userid=userid).execute()
360 original = copy.deepcopy(item)
361 item['object']['content'] = 'This is updated.'
362 service.activities.patch(postid=postid, userid=userid,
363 body=makepatch(original, item)).execute()
364 """
365 patch = {}
366 for key, original_value in original.iteritems():
367 modified_value = modified.get(key, None)
368 if modified_value is None:
369
370 patch[key] = None
371 elif original_value != modified_value:
372 if type(original_value) == type({}):
373
374 patch[key] = makepatch(original_value, modified_value)
375 else:
376
377 patch[key] = modified_value
378 else:
379
380 pass
381 for key in modified:
382 if key not in original:
383 patch[key] = modified[key]
384
385 return patch
386