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