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