1
2
3
4
5
6
7
8
9
10
11
12
13
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
40 raise NotImplementedError('You need to override this function')
41
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
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
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
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
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
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
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
201
202 if resp.status < 300:
203 if resp.status == 204:
204
205
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
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
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
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
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
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
273
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
288
289 @property
292
311
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
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
336 return body_value.SerializeToString()
337
339 return self._protocol_buffer.FromString(content)
340
341 @property
343 return self._protocol_buffer()
344
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
372 patch[key] = None
373 elif original_value != modified_value:
374 if type(original_value) == type({}):
375
376 patch[key] = makepatch(original_value, modified_value)
377 else:
378
379 patch[key] = modified_value
380 else:
381
382 pass
383 for key in modified:
384 if key not in original:
385 patch[key] = modified[key]
386
387 return patch
388