Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/googleapiclient/model.py: 39%
160 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:51 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:51 +0000
1# Copyright 2014 Google Inc. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
15"""Model objects for requests and responses.
17Each API may support one or more serializations, such
18as JSON, Atom, etc. The model classes are responsible
19for converting between the wire format and the Python
20object representation.
21"""
22from __future__ import absolute_import
24__author__ = "jcgregorio@google.com (Joe Gregorio)"
26import json
27import logging
28import platform
29import urllib
31from googleapiclient import version as googleapiclient_version
32from googleapiclient.errors import HttpError
34_LIBRARY_VERSION = googleapiclient_version.__version__
35_PY_VERSION = platform.python_version()
37LOGGER = logging.getLogger(__name__)
39dump_request_response = False
42def _abstract():
43 raise NotImplementedError("You need to override this function")
46class Model(object):
47 """Model base class.
49 All Model classes should implement this interface.
50 The Model serializes and de-serializes between a wire
51 format such as JSON and a Python object representation.
52 """
54 def request(self, headers, path_params, query_params, body_value):
55 """Updates outgoing requests with a serialized body.
57 Args:
58 headers: dict, request headers
59 path_params: dict, parameters that appear in the request path
60 query_params: dict, parameters that appear in the query
61 body_value: object, the request body as a Python object, which must be
62 serializable.
63 Returns:
64 A tuple of (headers, path_params, query, body)
66 headers: dict, request headers
67 path_params: dict, parameters that appear in the request path
68 query: string, query part of the request URI
69 body: string, the body serialized in the desired wire format.
70 """
71 _abstract()
73 def response(self, resp, content):
74 """Convert the response wire format into a Python object.
76 Args:
77 resp: httplib2.Response, the HTTP response headers and status
78 content: string, the body of the HTTP response
80 Returns:
81 The body de-serialized as a Python object.
83 Raises:
84 googleapiclient.errors.HttpError if a non 2xx response is received.
85 """
86 _abstract()
89class BaseModel(Model):
90 """Base model class.
92 Subclasses should provide implementations for the "serialize" and
93 "deserialize" methods, as well as values for the following class attributes.
95 Attributes:
96 accept: The value to use for the HTTP Accept header.
97 content_type: The value to use for the HTTP Content-type header.
98 no_content_response: The value to return when deserializing a 204 "No
99 Content" response.
100 alt_param: The value to supply as the "alt" query parameter for requests.
101 """
103 accept = None
104 content_type = None
105 no_content_response = None
106 alt_param = None
108 def _log_request(self, headers, path_params, query, body):
109 """Logs debugging information about the request if requested."""
110 if dump_request_response:
111 LOGGER.info("--request-start--")
112 LOGGER.info("-headers-start-")
113 for h, v in headers.items():
114 LOGGER.info("%s: %s", h, v)
115 LOGGER.info("-headers-end-")
116 LOGGER.info("-path-parameters-start-")
117 for h, v in path_params.items():
118 LOGGER.info("%s: %s", h, v)
119 LOGGER.info("-path-parameters-end-")
120 LOGGER.info("body: %s", body)
121 LOGGER.info("query: %s", query)
122 LOGGER.info("--request-end--")
124 def request(self, headers, path_params, query_params, body_value):
125 """Updates outgoing requests with a serialized body.
127 Args:
128 headers: dict, request headers
129 path_params: dict, parameters that appear in the request path
130 query_params: dict, parameters that appear in the query
131 body_value: object, the request body as a Python object, which must be
132 serializable by json.
133 Returns:
134 A tuple of (headers, path_params, query, body)
136 headers: dict, request headers
137 path_params: dict, parameters that appear in the request path
138 query: string, query part of the request URI
139 body: string, the body serialized as JSON
140 """
141 query = self._build_query(query_params)
142 headers["accept"] = self.accept
143 headers["accept-encoding"] = "gzip, deflate"
144 if "user-agent" in headers:
145 headers["user-agent"] += " "
146 else:
147 headers["user-agent"] = ""
148 headers["user-agent"] += "(gzip)"
149 if "x-goog-api-client" in headers:
150 headers["x-goog-api-client"] += " "
151 else:
152 headers["x-goog-api-client"] = ""
153 headers["x-goog-api-client"] += "gdcl/%s gl-python/%s" % (
154 _LIBRARY_VERSION,
155 _PY_VERSION,
156 )
158 if body_value is not None:
159 headers["content-type"] = self.content_type
160 body_value = self.serialize(body_value)
161 self._log_request(headers, path_params, query, body_value)
162 return (headers, path_params, query, body_value)
164 def _build_query(self, params):
165 """Builds a query string.
167 Args:
168 params: dict, the query parameters
170 Returns:
171 The query parameters properly encoded into an HTTP URI query string.
172 """
173 if self.alt_param is not None:
174 params.update({"alt": self.alt_param})
175 astuples = []
176 for key, value in params.items():
177 if type(value) == type([]):
178 for x in value:
179 x = x.encode("utf-8")
180 astuples.append((key, x))
181 else:
182 if isinstance(value, str) and callable(value.encode):
183 value = value.encode("utf-8")
184 astuples.append((key, value))
185 return "?" + urllib.parse.urlencode(astuples)
187 def _log_response(self, resp, content):
188 """Logs debugging information about the response if requested."""
189 if dump_request_response:
190 LOGGER.info("--response-start--")
191 for h, v in resp.items():
192 LOGGER.info("%s: %s", h, v)
193 if content:
194 LOGGER.info(content)
195 LOGGER.info("--response-end--")
197 def response(self, resp, content):
198 """Convert the response wire format into a Python object.
200 Args:
201 resp: httplib2.Response, the HTTP response headers and status
202 content: string, the body of the HTTP response
204 Returns:
205 The body de-serialized as a Python object.
207 Raises:
208 googleapiclient.errors.HttpError if a non 2xx response is received.
209 """
210 self._log_response(resp, content)
211 # Error handling is TBD, for example, do we retry
212 # for some operation/error combinations?
213 if resp.status < 300:
214 if resp.status == 204:
215 # A 204: No Content response should be treated differently
216 # to all the other success states
217 return self.no_content_response
218 return self.deserialize(content)
219 else:
220 LOGGER.debug("Content from bad request was: %r" % content)
221 raise HttpError(resp, content)
223 def serialize(self, body_value):
224 """Perform the actual Python object serialization.
226 Args:
227 body_value: object, the request body as a Python object.
229 Returns:
230 string, the body in serialized form.
231 """
232 _abstract()
234 def deserialize(self, content):
235 """Perform the actual deserialization from response string to Python
236 object.
238 Args:
239 content: string, the body of the HTTP response
241 Returns:
242 The body de-serialized as a Python object.
243 """
244 _abstract()
247class JsonModel(BaseModel):
248 """Model class for JSON.
250 Serializes and de-serializes between JSON and the Python
251 object representation of HTTP request and response bodies.
252 """
254 accept = "application/json"
255 content_type = "application/json"
256 alt_param = "json"
258 def __init__(self, data_wrapper=False):
259 """Construct a JsonModel.
261 Args:
262 data_wrapper: boolean, wrap requests and responses in a data wrapper
263 """
264 self._data_wrapper = data_wrapper
266 def serialize(self, body_value):
267 if (
268 isinstance(body_value, dict)
269 and "data" not in body_value
270 and self._data_wrapper
271 ):
272 body_value = {"data": body_value}
273 return json.dumps(body_value)
275 def deserialize(self, content):
276 try:
277 content = content.decode("utf-8")
278 except AttributeError:
279 pass
280 try:
281 body = json.loads(content)
282 except json.decoder.JSONDecodeError:
283 body = content
284 else:
285 if self._data_wrapper and "data" in body:
286 body = body["data"]
287 return body
289 @property
290 def no_content_response(self):
291 return {}
294class RawModel(JsonModel):
295 """Model class for requests that don't return JSON.
297 Serializes and de-serializes between JSON and the Python
298 object representation of HTTP request, and returns the raw bytes
299 of the response body.
300 """
302 accept = "*/*"
303 content_type = "application/json"
304 alt_param = None
306 def deserialize(self, content):
307 return content
309 @property
310 def no_content_response(self):
311 return ""
314class MediaModel(JsonModel):
315 """Model class for requests that return Media.
317 Serializes and de-serializes between JSON and the Python
318 object representation of HTTP request, and returns the raw bytes
319 of the response body.
320 """
322 accept = "*/*"
323 content_type = "application/json"
324 alt_param = "media"
326 def deserialize(self, content):
327 return content
329 @property
330 def no_content_response(self):
331 return ""
334class ProtocolBufferModel(BaseModel):
335 """Model class for protocol buffers.
337 Serializes and de-serializes the binary protocol buffer sent in the HTTP
338 request and response bodies.
339 """
341 accept = "application/x-protobuf"
342 content_type = "application/x-protobuf"
343 alt_param = "proto"
345 def __init__(self, protocol_buffer):
346 """Constructs a ProtocolBufferModel.
348 The serialized protocol buffer returned in an HTTP response will be
349 de-serialized using the given protocol buffer class.
351 Args:
352 protocol_buffer: The protocol buffer class used to de-serialize a
353 response from the API.
354 """
355 self._protocol_buffer = protocol_buffer
357 def serialize(self, body_value):
358 return body_value.SerializeToString()
360 def deserialize(self, content):
361 return self._protocol_buffer.FromString(content)
363 @property
364 def no_content_response(self):
365 return self._protocol_buffer()
368def makepatch(original, modified):
369 """Create a patch object.
371 Some methods support PATCH, an efficient way to send updates to a resource.
372 This method allows the easy construction of patch bodies by looking at the
373 differences between a resource before and after it was modified.
375 Args:
376 original: object, the original deserialized resource
377 modified: object, the modified deserialized resource
378 Returns:
379 An object that contains only the changes from original to modified, in a
380 form suitable to pass to a PATCH method.
382 Example usage:
383 item = service.activities().get(postid=postid, userid=userid).execute()
384 original = copy.deepcopy(item)
385 item['object']['content'] = 'This is updated.'
386 service.activities.patch(postid=postid, userid=userid,
387 body=makepatch(original, item)).execute()
388 """
389 patch = {}
390 for key, original_value in original.items():
391 modified_value = modified.get(key, None)
392 if modified_value is None:
393 # Use None to signal that the element is deleted
394 patch[key] = None
395 elif original_value != modified_value:
396 if type(original_value) == type({}):
397 # Recursively descend objects
398 patch[key] = makepatch(original_value, modified_value)
399 else:
400 # In the case of simple types or arrays we just replace
401 patch[key] = modified_value
402 else:
403 # Don't add anything to patch if there's no change
404 pass
405 for key in modified:
406 if key not in original:
407 patch[key] = modified[key]
409 return patch