| OLD | NEW |
| (Empty) | |
| 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. |
| 14 """Base class for api services.""" |
| 15 |
| 16 import contextlib |
| 17 import httplib |
| 18 import logging |
| 19 import pprint |
| 20 import types |
| 21 import urllib |
| 22 import urlparse |
| 23 |
| 24 |
| 25 from gslib.third_party.protorpc import message_types |
| 26 from gslib.third_party.protorpc import messages |
| 27 |
| 28 from gslib.third_party.storage_apitools import credentials_lib |
| 29 from gslib.third_party.storage_apitools import encoding |
| 30 from gslib.third_party.storage_apitools import exceptions |
| 31 from gslib.third_party.storage_apitools import http_wrapper |
| 32 from gslib.third_party.storage_apitools import util |
| 33 |
| 34 __all__ = [ |
| 35 'ApiMethodInfo', |
| 36 'ApiUploadInfo', |
| 37 'BaseApiClient', |
| 38 'BaseApiService', |
| 39 'NormalizeApiEndpoint', |
| 40 ] |
| 41 |
| 42 # TODO: Remove this once we quiet the spurious logging in |
| 43 # oauth2client (or drop oauth2client). |
| 44 logging.getLogger('oauth2client.util').setLevel(logging.ERROR) |
| 45 |
| 46 _MAX_URL_LENGTH = 2048 |
| 47 |
| 48 |
| 49 class ApiUploadInfo(messages.Message): |
| 50 """Media upload information for a method. |
| 51 |
| 52 Fields: |
| 53 accept: (repeated) MIME Media Ranges for acceptable media uploads |
| 54 to this method. |
| 55 max_size: (integer) Maximum size of a media upload, such as 3MB |
| 56 or 1TB (converted to an integer). |
| 57 resumable_path: Path to use for resumable uploads. |
| 58 resumable_multipart: (boolean) Whether or not the resumable endpoint |
| 59 supports multipart uploads. |
| 60 simple_path: Path to use for simple uploads. |
| 61 simple_multipart: (boolean) Whether or not the simple endpoint |
| 62 supports multipart uploads. |
| 63 """ |
| 64 accept = messages.StringField(1, repeated=True) |
| 65 max_size = messages.IntegerField(2) |
| 66 resumable_path = messages.StringField(3) |
| 67 resumable_multipart = messages.BooleanField(4) |
| 68 simple_path = messages.StringField(5) |
| 69 simple_multipart = messages.BooleanField(6) |
| 70 |
| 71 |
| 72 class ApiMethodInfo(messages.Message): |
| 73 """Configuration info for an API method. |
| 74 |
| 75 All fields are strings unless noted otherwise. |
| 76 |
| 77 Fields: |
| 78 relative_path: Relative path for this method. |
| 79 method_id: ID for this method. |
| 80 http_method: HTTP verb to use for this method. |
| 81 path_params: (repeated) path parameters for this method. |
| 82 query_params: (repeated) query parameters for this method. |
| 83 ordered_params: (repeated) ordered list of parameters for |
| 84 this method. |
| 85 description: description of this method. |
| 86 request_type_name: name of the request type. |
| 87 response_type_name: name of the response type. |
| 88 request_field: if not null, the field to pass as the body |
| 89 of this POST request. may also be the REQUEST_IS_BODY |
| 90 value below to indicate the whole message is the body. |
| 91 upload_config: (ApiUploadInfo) Information about the upload |
| 92 configuration supported by this method. |
| 93 supports_download: (boolean) If True, this method supports |
| 94 downloading the request via the `alt=media` query |
| 95 parameter. |
| 96 """ |
| 97 |
| 98 relative_path = messages.StringField(1) |
| 99 method_id = messages.StringField(2) |
| 100 http_method = messages.StringField(3) |
| 101 path_params = messages.StringField(4, repeated=True) |
| 102 query_params = messages.StringField(5, repeated=True) |
| 103 ordered_params = messages.StringField(6, repeated=True) |
| 104 description = messages.StringField(7) |
| 105 request_type_name = messages.StringField(8) |
| 106 response_type_name = messages.StringField(9) |
| 107 request_field = messages.StringField(10, default='') |
| 108 upload_config = messages.MessageField(ApiUploadInfo, 11) |
| 109 supports_download = messages.BooleanField(12, default=False) |
| 110 REQUEST_IS_BODY = '<request>' |
| 111 |
| 112 |
| 113 def _LoadClass(name, messages_module): |
| 114 if name.startswith('message_types.'): |
| 115 _, _, classname = name.partition('.') |
| 116 return getattr(message_types, classname) |
| 117 elif '.' not in name: |
| 118 return getattr(messages_module, name) |
| 119 else: |
| 120 raise exceptions.GeneratedClientError('Unknown class %s' % name) |
| 121 |
| 122 |
| 123 def _RequireClassAttrs(obj, attrs): |
| 124 for attr in attrs: |
| 125 attr_name = attr.upper() |
| 126 if not hasattr(obj, '%s' % attr_name) or not getattr(obj, attr_name): |
| 127 msg = 'No %s specified for object of class %s.' % ( |
| 128 attr_name, type(obj).__name__) |
| 129 raise exceptions.GeneratedClientError(msg) |
| 130 |
| 131 |
| 132 def NormalizeApiEndpoint(api_endpoint): |
| 133 if not api_endpoint.endswith('/'): |
| 134 api_endpoint += '/' |
| 135 return api_endpoint |
| 136 |
| 137 |
| 138 class _UrlBuilder(object): |
| 139 """Convenient container for url data.""" |
| 140 |
| 141 def __init__(self, base_url, relative_path=None, query_params=None): |
| 142 components = urlparse.urlsplit(urlparse.urljoin( |
| 143 base_url, relative_path or '')) |
| 144 if components.fragment: |
| 145 raise exceptions.ConfigurationValueError( |
| 146 'Unexpected url fragment: %s' % components.fragment) |
| 147 self.query_params = urlparse.parse_qs(components.query or '') |
| 148 if query_params is not None: |
| 149 self.query_params.update(query_params) |
| 150 self.__scheme = components.scheme |
| 151 self.__netloc = components.netloc |
| 152 self.relative_path = components.path |
| 153 |
| 154 @classmethod |
| 155 def FromUrl(cls, url): |
| 156 urlparts = urlparse.urlsplit(url) |
| 157 query_params = urlparse.parse_qs(urlparts.query) |
| 158 base_url = urlparse.urlunsplit(( |
| 159 urlparts.scheme, urlparts.netloc, '', None, None)) |
| 160 relative_path = urlparts.path |
| 161 return cls(base_url, relative_path=relative_path, query_params=query_params) |
| 162 |
| 163 @property |
| 164 def base_url(self): |
| 165 return urlparse.urlunsplit((self.__scheme, self.__netloc, '', '', '')) |
| 166 |
| 167 @base_url.setter |
| 168 def base_url(self, value): |
| 169 components = urlparse.urlsplit(value) |
| 170 if components.path or components.query or components.fragment: |
| 171 raise exceptions.ConfigurationValueError('Invalid base url: %s' % value) |
| 172 self.__scheme = components.scheme |
| 173 self.__netloc = components.netloc |
| 174 |
| 175 @property |
| 176 def query(self): |
| 177 # TODO: In the case that some of the query params are |
| 178 # non-ASCII, we may silently fail to encode correctly. We should |
| 179 # figure out who is responsible for owning the object -> str |
| 180 # conversion. |
| 181 return urllib.urlencode(self.query_params, doseq=True) |
| 182 |
| 183 @property |
| 184 def url(self): |
| 185 if '{' in self.relative_path or '}' in self.relative_path: |
| 186 raise exceptions.ConfigurationValueError( |
| 187 'Cannot create url with relative path %s' % self.relative_path) |
| 188 return urlparse.urlunsplit(( |
| 189 self.__scheme, self.__netloc, self.relative_path, self.query, '')) |
| 190 |
| 191 |
| 192 class BaseApiClient(object): |
| 193 """Base class for client libraries.""" |
| 194 MESSAGES_MODULE = None |
| 195 |
| 196 _API_KEY = '' |
| 197 _CLIENT_ID = '' |
| 198 _CLIENT_SECRET = '' |
| 199 _PACKAGE = '' |
| 200 _SCOPES = [] |
| 201 _USER_AGENT = '' |
| 202 |
| 203 def __init__(self, url, credentials=None, get_credentials=True, http=None, |
| 204 model=None, log_request=False, log_response=False, num_retries=5, |
| 205 credentials_args=None, default_global_params=None): |
| 206 _RequireClassAttrs(self, ( |
| 207 '_package', '_scopes', '_client_id', '_client_secret', |
| 208 'messages_module')) |
| 209 if default_global_params is not None: |
| 210 util.Typecheck(default_global_params, self.params_type) |
| 211 self.__default_global_params = default_global_params |
| 212 self.log_request = log_request |
| 213 self.log_response = log_response |
| 214 self.__num_retries = 5 |
| 215 # We let the @property machinery below do our validation. |
| 216 self.num_retries = num_retries |
| 217 self._url = url |
| 218 self._credentials = credentials |
| 219 if get_credentials and not credentials: |
| 220 credentials_args = credentials_args or {} |
| 221 self._SetCredentials(**credentials_args) |
| 222 self._http = http or http_wrapper.GetHttp() |
| 223 # Note that "no credentials" is totally possible. |
| 224 if self._credentials is not None: |
| 225 self._http = self._credentials.authorize(self._http) |
| 226 # TODO: Remove this field when we switch to proto2. |
| 227 self.__include_fields = None |
| 228 |
| 229 # TODO: Finish deprecating these fields. |
| 230 _ = model |
| 231 |
| 232 def _SetCredentials(self, **kwds): |
| 233 """Fetch credentials, and set them for this client. |
| 234 |
| 235 Note that we can't simply return credentials, since creating them |
| 236 may involve side-effecting self. |
| 237 |
| 238 Args: |
| 239 **kwds: Additional keyword arguments are passed on to GetCredentials. |
| 240 |
| 241 Returns: |
| 242 None. Sets self._credentials. |
| 243 """ |
| 244 args = { |
| 245 'api_key': self._API_KEY, |
| 246 'client': self, |
| 247 'client_id': self._CLIENT_ID, |
| 248 'client_secret': self._CLIENT_SECRET, |
| 249 'package_name': self._PACKAGE, |
| 250 'scopes': self._SCOPES, |
| 251 'user_agent': self._USER_AGENT, |
| 252 } |
| 253 args.update(kwds) |
| 254 # TODO: It's a bit dangerous to pass this |
| 255 # still-half-initialized self into this method, but we might need |
| 256 # to set attributes on it associated with our credentials. |
| 257 # Consider another way around this (maybe a callback?) and whether |
| 258 # or not it's worth it. |
| 259 self._credentials = credentials_lib.GetCredentials(**args) |
| 260 |
| 261 @classmethod |
| 262 def ClientInfo(cls): |
| 263 return { |
| 264 'client_id': cls._CLIENT_ID, |
| 265 'client_secret': cls._CLIENT_SECRET, |
| 266 'scope': ' '.join(sorted(util.NormalizeScopes(cls._SCOPES))), |
| 267 'user_agent': cls._USER_AGENT, |
| 268 } |
| 269 |
| 270 @property |
| 271 def base_model_class(self): |
| 272 return None |
| 273 |
| 274 @property |
| 275 def http(self): |
| 276 return self._http |
| 277 |
| 278 @property |
| 279 def url(self): |
| 280 return self._url |
| 281 |
| 282 @classmethod |
| 283 def GetScopes(cls): |
| 284 return cls._SCOPES |
| 285 |
| 286 @property |
| 287 def params_type(self): |
| 288 return _LoadClass('StandardQueryParameters', self.MESSAGES_MODULE) |
| 289 |
| 290 @property |
| 291 def user_agent(self): |
| 292 return self._USER_AGENT |
| 293 |
| 294 @property |
| 295 def _default_global_params(self): |
| 296 if self.__default_global_params is None: |
| 297 self.__default_global_params = self.params_type() |
| 298 return self.__default_global_params |
| 299 |
| 300 def AddGlobalParam(self, name, value): |
| 301 params = self._default_global_params |
| 302 setattr(params, name, value) |
| 303 |
| 304 @property |
| 305 def global_params(self): |
| 306 return encoding.CopyProtoMessage(self._default_global_params) |
| 307 |
| 308 @contextlib.contextmanager |
| 309 def IncludeFields(self, include_fields): |
| 310 self.__include_fields = include_fields |
| 311 yield |
| 312 self.__include_fields = None |
| 313 |
| 314 @property |
| 315 def num_retries(self): |
| 316 return self.__num_retries |
| 317 |
| 318 @num_retries.setter |
| 319 def num_retries(self, value): |
| 320 util.Typecheck(value, (int, long)) |
| 321 if value < 0: |
| 322 raise exceptions.InvalidDataError( |
| 323 'Cannot have negative value for num_retries') |
| 324 self.__num_retries = value |
| 325 |
| 326 @contextlib.contextmanager |
| 327 def WithRetries(self, num_retries): |
| 328 old_num_retries = self.num_retries |
| 329 self.num_retries = num_retries |
| 330 yield |
| 331 self.num_retries = old_num_retries |
| 332 |
| 333 def ProcessRequest(self, method_config, request): |
| 334 """Hook for pre-processing of requests.""" |
| 335 if self.log_request: |
| 336 logging.info( |
| 337 'Calling method %s with %s: %s', method_config.method_id, |
| 338 method_config.request_type_name, request) |
| 339 return request |
| 340 |
| 341 def ProcessHttpRequest(self, http_request): |
| 342 """Hook for pre-processing of http requests.""" |
| 343 if self.log_request: |
| 344 logging.info('Making http %s to %s', |
| 345 http_request.http_method, http_request.url) |
| 346 logging.info('Headers: %s', pprint.pformat(http_request.headers)) |
| 347 if http_request.body: |
| 348 # TODO: Make this safe to print in the case of |
| 349 # non-printable body characters. |
| 350 logging.info('Body:\n%s', |
| 351 http_request.loggable_body or http_request.body) |
| 352 else: |
| 353 logging.info('Body: (none)') |
| 354 return http_request |
| 355 |
| 356 def ProcessResponse(self, method_config, response): |
| 357 if self.log_response: |
| 358 logging.info('Response of type %s: %s', |
| 359 method_config.response_type_name, response) |
| 360 return response |
| 361 |
| 362 # TODO: Decide where these two functions should live. |
| 363 def SerializeMessage(self, message): |
| 364 return encoding.MessageToJson(message, include_fields=self.__include_fields) |
| 365 |
| 366 def DeserializeMessage(self, response_type, data): |
| 367 """Deserialize the given data as method_config.response_type.""" |
| 368 try: |
| 369 message = encoding.JsonToMessage(response_type, data) |
| 370 except (exceptions.InvalidDataFromServerError, |
| 371 messages.ValidationError) as e: |
| 372 raise exceptions.InvalidDataFromServerError( |
| 373 'Error decoding response "%s" as type %s: %s' % ( |
| 374 data, response_type.__name__, e)) |
| 375 return message |
| 376 |
| 377 def FinalizeTransferUrl(self, url): |
| 378 """Modify the url for a given transfer, based on auth and version.""" |
| 379 url_builder = _UrlBuilder.FromUrl(url) |
| 380 if self.global_params.key: |
| 381 url_builder.query_params['key'] = self.global_params.key |
| 382 return url_builder.url |
| 383 |
| 384 |
| 385 class BaseApiService(object): |
| 386 """Base class for generated API services.""" |
| 387 |
| 388 def __init__(self, client): |
| 389 self.__client = client |
| 390 self._method_configs = {} |
| 391 self._upload_configs = {} |
| 392 |
| 393 @property |
| 394 def _client(self): |
| 395 return self.__client |
| 396 |
| 397 def GetMethodConfig(self, method): |
| 398 return self._method_configs[method] |
| 399 |
| 400 def GetUploadConfig(self, method): |
| 401 return self._upload_configs.get(method) |
| 402 |
| 403 def GetRequestType(self, method): |
| 404 method_config = self.GetMethodConfig(method) |
| 405 return getattr(self._client.MESSAGES_MODULE, |
| 406 method_config.request_type_name) |
| 407 |
| 408 def GetResponseType(self, method): |
| 409 method_config = self.GetMethodConfig(method) |
| 410 return getattr(self._client.MESSAGES_MODULE, |
| 411 method_config.response_type_name) |
| 412 |
| 413 def __CombineGlobalParams(self, global_params, default_params): |
| 414 util.Typecheck(global_params, (types.NoneType, self.__client.params_type)) |
| 415 result = self.__client.params_type() |
| 416 global_params = global_params or self.__client.params_type() |
| 417 for field in result.all_fields(): |
| 418 value = (global_params.get_assigned_value(field.name) or |
| 419 default_params.get_assigned_value(field.name)) |
| 420 if value not in (None, [], ()): |
| 421 setattr(result, field.name, value) |
| 422 return result |
| 423 |
| 424 def __ConstructQueryParams(self, query_params, request, global_params): |
| 425 """Construct a dictionary of query parameters for this request.""" |
| 426 global_params = self.__CombineGlobalParams( |
| 427 global_params, self.__client.global_params) |
| 428 query_info = dict((field.name, getattr(global_params, field.name)) |
| 429 for field in self.__client.params_type.all_fields()) |
| 430 query_info.update( |
| 431 (param, getattr(request, param, None)) for param in query_params) |
| 432 query_info = dict((k, v) for k, v in query_info.iteritems() |
| 433 if v is not None) |
| 434 for k, v in query_info.iteritems(): |
| 435 if isinstance(v, unicode): |
| 436 query_info[k] = v.encode('utf8') |
| 437 elif isinstance(v, str): |
| 438 query_info[k] = v.decode('utf8') |
| 439 return query_info |
| 440 |
| 441 def __ConstructRelativePath(self, method_config, request, relative_path=None): |
| 442 """Determine the relative path for request.""" |
| 443 path = relative_path or method_config.relative_path |
| 444 path = path.replace('+', '') |
| 445 for param in method_config.path_params: |
| 446 param_template = '{%s}' % param |
| 447 if param_template not in path: |
| 448 raise exceptions.InvalidUserInputError( |
| 449 'Missing path parameter %s' % param) |
| 450 try: |
| 451 # TODO: Do we want to support some sophisticated |
| 452 # mapping here? |
| 453 value = getattr(request, param) |
| 454 except AttributeError: |
| 455 raise exceptions.InvalidUserInputError( |
| 456 'Request missing required parameter %s' % param) |
| 457 if value is None: |
| 458 raise exceptions.InvalidUserInputError( |
| 459 'Request missing required parameter %s' % param) |
| 460 try: |
| 461 if not isinstance(value, basestring): |
| 462 value = str(value) |
| 463 path = path.replace(param_template, |
| 464 urllib.quote(value.encode('utf_8'), '')) |
| 465 except TypeError as e: |
| 466 raise exceptions.InvalidUserInputError( |
| 467 'Error setting required parameter %s to value %s: %s' % ( |
| 468 param, value, e)) |
| 469 return path |
| 470 |
| 471 def __FinalizeRequest(self, http_request, url_builder): |
| 472 """Make any final general adjustments to the request.""" |
| 473 if (http_request.http_method == 'GET' and |
| 474 len(http_request.url) > _MAX_URL_LENGTH): |
| 475 http_request.http_method = 'POST' |
| 476 http_request.headers['x-http-method-override'] = 'GET' |
| 477 http_request.headers['content-type'] = 'application/x-www-form-urlencoded' |
| 478 http_request.body = url_builder.query |
| 479 url_builder.query_params = {} |
| 480 http_request.url = url_builder.url |
| 481 |
| 482 def __ProcessHttpResponse(self, method_config, http_response): |
| 483 """Process the given http response.""" |
| 484 if http_response.status_code not in (httplib.OK, httplib.NO_CONTENT): |
| 485 raise exceptions.HttpError.FromResponse(http_response) |
| 486 if http_response.status_code == httplib.NO_CONTENT: |
| 487 # TODO: Find out why _replace doesn't seem to work here. |
| 488 http_response = http_wrapper.Response( |
| 489 info=http_response.info, content='{}', |
| 490 request_url=http_response.request_url) |
| 491 response_type = _LoadClass( |
| 492 method_config.response_type_name, self.__client.MESSAGES_MODULE) |
| 493 return self.__client.DeserializeMessage( |
| 494 response_type, http_response.content) |
| 495 |
| 496 def __SetBaseHeaders(self, http_request, client): |
| 497 """Fill in the basic headers on http_request.""" |
| 498 # TODO: Make the default a little better here, and |
| 499 # include the apitools version. |
| 500 user_agent = client.user_agent or 'apitools-client/1.0' |
| 501 http_request.headers['user-agent'] = user_agent |
| 502 http_request.headers['accept'] = 'application/json' |
| 503 http_request.headers['accept-encoding'] = 'gzip, deflate' |
| 504 |
| 505 def __SetBody(self, http_request, method_config, request, upload): |
| 506 """Fill in the body on http_request.""" |
| 507 if not method_config.request_field: |
| 508 return |
| 509 |
| 510 request_type = _LoadClass( |
| 511 method_config.request_type_name, self.__client.MESSAGES_MODULE) |
| 512 if method_config.request_field == REQUEST_IS_BODY: |
| 513 body_value = request |
| 514 body_type = request_type |
| 515 else: |
| 516 body_value = getattr(request, method_config.request_field) |
| 517 body_field = request_type.field_by_name(method_config.request_field) |
| 518 util.Typecheck(body_field, messages.MessageField) |
| 519 body_type = body_field.type |
| 520 |
| 521 if upload and not body_value: |
| 522 # We're going to fill in the body later. |
| 523 return |
| 524 util.Typecheck(body_value, body_type) |
| 525 http_request.headers['content-type'] = 'application/json' |
| 526 http_request.body = self.__client.SerializeMessage(body_value) |
| 527 |
| 528 def PrepareHttpRequest(self, method_config, request, global_params=None, |
| 529 upload=None, upload_config=None, download=None): |
| 530 """Prepares an HTTP request to be sent.""" |
| 531 request_type = _LoadClass( |
| 532 method_config.request_type_name, self.__client.MESSAGES_MODULE) |
| 533 util.Typecheck(request, request_type) |
| 534 request = self.__client.ProcessRequest(method_config, request) |
| 535 |
| 536 http_request = http_wrapper.Request(http_method=method_config.http_method) |
| 537 self.__SetBaseHeaders(http_request, self.__client) |
| 538 self.__SetBody(http_request, method_config, request, upload) |
| 539 |
| 540 url_builder = _UrlBuilder( |
| 541 self.__client.url, relative_path=method_config.relative_path) |
| 542 url_builder.query_params = self.__ConstructQueryParams( |
| 543 method_config.query_params, request, global_params) |
| 544 |
| 545 # It's important that upload and download go before we fill in the |
| 546 # relative path, so that they can replace it. |
| 547 if upload is not None: |
| 548 upload.ConfigureRequest(upload_config, http_request, url_builder) |
| 549 if download is not None: |
| 550 download.ConfigureRequest(http_request, url_builder) |
| 551 |
| 552 url_builder.relative_path = self.__ConstructRelativePath( |
| 553 method_config, request, relative_path=url_builder.relative_path) |
| 554 self.__FinalizeRequest(http_request, url_builder) |
| 555 |
| 556 return self.__client.ProcessHttpRequest(http_request) |
| 557 |
| 558 def _RunMethod(self, method_config, request, global_params=None, |
| 559 upload=None, upload_config=None, download=None): |
| 560 """Call this method with request.""" |
| 561 if upload is not None and download is not None: |
| 562 # TODO: This just involves refactoring the logic |
| 563 # below into callbacks that we can pass around; in particular, |
| 564 # the order should be that the upload gets the initial request, |
| 565 # and then passes its reply to a download if one exists, and |
| 566 # then that goes to ProcessResponse and is returned. |
| 567 raise exceptions.NotYetImplementedError( |
| 568 'Cannot yet use both upload and download at once') |
| 569 |
| 570 http_request = self.PrepareHttpRequest( |
| 571 method_config, request, global_params, upload, upload_config, download) |
| 572 |
| 573 # TODO: Make num_retries customizable on Transfer |
| 574 # objects, and pass in self.__client.num_retries when initializing |
| 575 # an upload or download. |
| 576 if download is not None: |
| 577 download.InitializeDownload(http_request, client=self._client) |
| 578 return |
| 579 |
| 580 http_response = None |
| 581 if upload is not None: |
| 582 http_response = upload.InitializeUpload(http_request, client=self._client) |
| 583 if http_response is None: |
| 584 http = self.__client.http |
| 585 if upload and upload.bytes_http: |
| 586 http = upload.bytes_http |
| 587 http_response = http_wrapper.MakeRequest( |
| 588 http, http_request, retries=self.__client.num_retries) |
| 589 |
| 590 return self.ProcessHttpResponse(method_config, http_response) |
| 591 |
| 592 def ProcessHttpResponse(self, method_config, http_response): |
| 593 """Convert an HTTP response to the expected message type.""" |
| 594 return self.__client.ProcessResponse( |
| 595 method_config, |
| 596 self.__ProcessHttpResponse(method_config, http_response)) |
| OLD | NEW |