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