| #!/usr/bin/env python |
| # |
| # Copyright 2015 Google Inc. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Base class for api services.""" |
| |
| import base64 |
| import contextlib |
| import datetime |
| import logging |
| import pprint |
| |
| |
| import six |
| from six.moves import http_client |
| from six.moves import urllib |
| |
| |
| from apitools.base.protorpclite import message_types |
| from apitools.base.protorpclite import messages |
| from apitools.base.py import encoding |
| from apitools.base.py import exceptions |
| from apitools.base.py import http_wrapper |
| from apitools.base.py import util |
| |
| __all__ = [ |
| 'ApiMethodInfo', |
| 'ApiUploadInfo', |
| 'BaseApiClient', |
| 'BaseApiService', |
| 'NormalizeApiEndpoint', |
| ] |
| |
| # TODO(craigcitro): Remove this once we quiet the spurious logging in |
| # oauth2client (or drop oauth2client). |
| logging.getLogger('oauth2client.util').setLevel(logging.ERROR) |
| |
| _MAX_URL_LENGTH = 2048 |
| |
| |
| class ApiUploadInfo(messages.Message): |
| |
| """Media upload information for a method. |
| |
| Fields: |
| accept: (repeated) MIME Media Ranges for acceptable media uploads |
| to this method. |
| max_size: (integer) Maximum size of a media upload, such as 3MB |
| or 1TB (converted to an integer). |
| resumable_path: Path to use for resumable uploads. |
| resumable_multipart: (boolean) Whether or not the resumable endpoint |
| supports multipart uploads. |
| simple_path: Path to use for simple uploads. |
| simple_multipart: (boolean) Whether or not the simple endpoint |
| supports multipart uploads. |
| """ |
| accept = messages.StringField(1, repeated=True) |
| max_size = messages.IntegerField(2) |
| resumable_path = messages.StringField(3) |
| resumable_multipart = messages.BooleanField(4) |
| simple_path = messages.StringField(5) |
| simple_multipart = messages.BooleanField(6) |
| |
| |
| class ApiMethodInfo(messages.Message): |
| |
| """Configuration info for an API method. |
| |
| All fields are strings unless noted otherwise. |
| |
| Fields: |
| relative_path: Relative path for this method. |
| flat_path: Expanded version (if any) of relative_path. |
| method_id: ID for this method. |
| http_method: HTTP verb to use for this method. |
| path_params: (repeated) path parameters for this method. |
| query_params: (repeated) query parameters for this method. |
| ordered_params: (repeated) ordered list of parameters for |
| this method. |
| description: description of this method. |
| request_type_name: name of the request type. |
| response_type_name: name of the response type. |
| request_field: if not null, the field to pass as the body |
| of this POST request. may also be the REQUEST_IS_BODY |
| value below to indicate the whole message is the body. |
| upload_config: (ApiUploadInfo) Information about the upload |
| configuration supported by this method. |
| supports_download: (boolean) If True, this method supports |
| downloading the request via the `alt=media` query |
| parameter. |
| """ |
| |
| relative_path = messages.StringField(1) |
| flat_path = messages.StringField(2) |
| method_id = messages.StringField(3) |
| http_method = messages.StringField(4) |
| path_params = messages.StringField(5, repeated=True) |
| query_params = messages.StringField(6, repeated=True) |
| ordered_params = messages.StringField(7, repeated=True) |
| description = messages.StringField(8) |
| request_type_name = messages.StringField(9) |
| response_type_name = messages.StringField(10) |
| request_field = messages.StringField(11, default='') |
| upload_config = messages.MessageField(ApiUploadInfo, 12) |
| supports_download = messages.BooleanField(13, default=False) |
| REQUEST_IS_BODY = '<request>' |
| |
| |
| def _LoadClass(name, messages_module): |
| if name.startswith('message_types.'): |
| _, _, classname = name.partition('.') |
| return getattr(message_types, classname) |
| elif '.' not in name: |
| return getattr(messages_module, name) |
| else: |
| raise exceptions.GeneratedClientError('Unknown class %s' % name) |
| |
| |
| def _RequireClassAttrs(obj, attrs): |
| for attr in attrs: |
| attr_name = attr.upper() |
| if not hasattr(obj, '%s' % attr_name) or not getattr(obj, attr_name): |
| msg = 'No %s specified for object of class %s.' % ( |
| attr_name, type(obj).__name__) |
| raise exceptions.GeneratedClientError(msg) |
| |
| |
| def NormalizeApiEndpoint(api_endpoint): |
| if not api_endpoint.endswith('/'): |
| api_endpoint += '/' |
| return api_endpoint |
| |
| |
| def _urljoin(base, url): # pylint: disable=invalid-name |
| """Custom urljoin replacement supporting : before / in url.""" |
| # In general, it's unsafe to simply join base and url. However, for |
| # the case of discovery documents, we know: |
| # * base will never contain params, query, or fragment |
| # * url will never contain a scheme or net_loc. |
| # In general, this means we can safely join on /; we just need to |
| # ensure we end up with precisely one / joining base and url. The |
| # exception here is the case of media uploads, where url will be an |
| # absolute url. |
| if url.startswith('http://') or url.startswith('https://'): |
| return urllib.parse.urljoin(base, url) |
| new_base = base if base.endswith('/') else base + '/' |
| new_url = url[1:] if url.startswith('/') else url |
| return new_base + new_url |
| |
| |
| class _UrlBuilder(object): |
| |
| """Convenient container for url data.""" |
| |
| def __init__(self, base_url, relative_path=None, query_params=None): |
| components = urllib.parse.urlsplit(_urljoin( |
| base_url, relative_path or '')) |
| if components.fragment: |
| raise exceptions.ConfigurationValueError( |
| 'Unexpected url fragment: %s' % components.fragment) |
| self.query_params = urllib.parse.parse_qs(components.query or '') |
| if query_params is not None: |
| self.query_params.update(query_params) |
| self.__scheme = components.scheme |
| self.__netloc = components.netloc |
| self.relative_path = components.path or '' |
| |
| @classmethod |
| def FromUrl(cls, url): |
| urlparts = urllib.parse.urlsplit(url) |
| query_params = urllib.parse.parse_qs(urlparts.query) |
| base_url = urllib.parse.urlunsplit(( |
| urlparts.scheme, urlparts.netloc, '', None, None)) |
| relative_path = urlparts.path or '' |
| return cls( |
| base_url, relative_path=relative_path, query_params=query_params) |
| |
| @property |
| def base_url(self): |
| return urllib.parse.urlunsplit( |
| (self.__scheme, self.__netloc, '', '', '')) |
| |
| @base_url.setter |
| def base_url(self, value): |
| components = urllib.parse.urlsplit(value) |
| if components.path or components.query or components.fragment: |
| raise exceptions.ConfigurationValueError( |
| 'Invalid base url: %s' % value) |
| self.__scheme = components.scheme |
| self.__netloc = components.netloc |
| |
| @property |
| def query(self): |
| # TODO(craigcitro): In the case that some of the query params are |
| # non-ASCII, we may silently fail to encode correctly. We should |
| # figure out who is responsible for owning the object -> str |
| # conversion. |
| return urllib.parse.urlencode(self.query_params, True) |
| |
| @property |
| def url(self): |
| if '{' in self.relative_path or '}' in self.relative_path: |
| raise exceptions.ConfigurationValueError( |
| 'Cannot create url with relative path %s' % self.relative_path) |
| return urllib.parse.urlunsplit(( |
| self.__scheme, self.__netloc, self.relative_path, self.query, '')) |
| |
| |
| def _SkipGetCredentials(): |
| """Hook for skipping credentials. For internal use.""" |
| return False |
| |
| |
| class BaseApiClient(object): |
| |
| """Base class for client libraries.""" |
| MESSAGES_MODULE = None |
| |
| _API_KEY = '' |
| _CLIENT_ID = '' |
| _CLIENT_SECRET = '' |
| _PACKAGE = '' |
| _SCOPES = [] |
| _USER_AGENT = '' |
| |
| def __init__(self, url, credentials=None, get_credentials=True, http=None, |
| model=None, log_request=False, log_response=False, |
| num_retries=5, max_retry_wait=60, credentials_args=None, |
| default_global_params=None, additional_http_headers=None, |
| check_response_func=None, retry_func=None): |
| _RequireClassAttrs(self, ('_package', '_scopes', 'messages_module')) |
| if default_global_params is not None: |
| util.Typecheck(default_global_params, self.params_type) |
| self.__default_global_params = default_global_params |
| self.log_request = log_request |
| self.log_response = log_response |
| self.__num_retries = 5 |
| self.__max_retry_wait = 60 |
| # We let the @property machinery below do our validation. |
| self.num_retries = num_retries |
| self.max_retry_wait = max_retry_wait |
| self._credentials = credentials |
| get_credentials = get_credentials and not _SkipGetCredentials() |
| if get_credentials and not credentials: |
| credentials_args = credentials_args or {} |
| self._SetCredentials(**credentials_args) |
| self._url = NormalizeApiEndpoint(url) |
| self._http = http or http_wrapper.GetHttp() |
| # Note that "no credentials" is totally possible. |
| if self._credentials is not None: |
| self._http = self._credentials.authorize(self._http) |
| # TODO(craigcitro): Remove this field when we switch to proto2. |
| self.__include_fields = None |
| |
| self.additional_http_headers = additional_http_headers or {} |
| self.check_response_func = check_response_func |
| self.retry_func = retry_func |
| |
| # TODO(craigcitro): Finish deprecating these fields. |
| _ = model |
| |
| self.__response_type_model = 'proto' |
| |
| def _SetCredentials(self, **kwds): |
| """Fetch credentials, and set them for this client. |
| |
| Note that we can't simply return credentials, since creating them |
| may involve side-effecting self. |
| |
| Args: |
| **kwds: Additional keyword arguments are passed on to GetCredentials. |
| |
| Returns: |
| None. Sets self._credentials. |
| """ |
| args = { |
| 'api_key': self._API_KEY, |
| 'client': self, |
| 'client_id': self._CLIENT_ID, |
| 'client_secret': self._CLIENT_SECRET, |
| 'package_name': self._PACKAGE, |
| 'scopes': self._SCOPES, |
| 'user_agent': self._USER_AGENT, |
| } |
| args.update(kwds) |
| # credentials_lib can be expensive to import so do it only if needed. |
| from apitools.base.py import credentials_lib |
| # TODO(craigcitro): It's a bit dangerous to pass this |
| # still-half-initialized self into this method, but we might need |
| # to set attributes on it associated with our credentials. |
| # Consider another way around this (maybe a callback?) and whether |
| # or not it's worth it. |
| self._credentials = credentials_lib.GetCredentials(**args) |
| |
| @classmethod |
| def ClientInfo(cls): |
| return { |
| 'client_id': cls._CLIENT_ID, |
| 'client_secret': cls._CLIENT_SECRET, |
| 'scope': ' '.join(sorted(util.NormalizeScopes(cls._SCOPES))), |
| 'user_agent': cls._USER_AGENT, |
| } |
| |
| @property |
| def base_model_class(self): |
| return None |
| |
| @property |
| def http(self): |
| return self._http |
| |
| @property |
| def url(self): |
| return self._url |
| |
| @classmethod |
| def GetScopes(cls): |
| return cls._SCOPES |
| |
| @property |
| def params_type(self): |
| return _LoadClass('StandardQueryParameters', self.MESSAGES_MODULE) |
| |
| @property |
| def user_agent(self): |
| return self._USER_AGENT |
| |
| @property |
| def _default_global_params(self): |
| if self.__default_global_params is None: |
| # pylint: disable=not-callable |
| self.__default_global_params = self.params_type() |
| return self.__default_global_params |
| |
| def AddGlobalParam(self, name, value): |
| params = self._default_global_params |
| setattr(params, name, value) |
| |
| @property |
| def global_params(self): |
| return encoding.CopyProtoMessage(self._default_global_params) |
| |
| @contextlib.contextmanager |
| def IncludeFields(self, include_fields): |
| self.__include_fields = include_fields |
| yield |
| self.__include_fields = None |
| |
| @property |
| def response_type_model(self): |
| return self.__response_type_model |
| |
| @contextlib.contextmanager |
| def JsonResponseModel(self): |
| """In this context, return raw JSON instead of proto.""" |
| old_model = self.response_type_model |
| self.__response_type_model = 'json' |
| yield |
| self.__response_type_model = old_model |
| |
| @property |
| def num_retries(self): |
| return self.__num_retries |
| |
| @num_retries.setter |
| def num_retries(self, value): |
| util.Typecheck(value, six.integer_types) |
| if value < 0: |
| raise exceptions.InvalidDataError( |
| 'Cannot have negative value for num_retries') |
| self.__num_retries = value |
| |
| @property |
| def max_retry_wait(self): |
| return self.__max_retry_wait |
| |
| @max_retry_wait.setter |
| def max_retry_wait(self, value): |
| util.Typecheck(value, six.integer_types) |
| if value <= 0: |
| raise exceptions.InvalidDataError( |
| 'max_retry_wait must be a postiive integer') |
| self.__max_retry_wait = value |
| |
| @contextlib.contextmanager |
| def WithRetries(self, num_retries): |
| old_num_retries = self.num_retries |
| self.num_retries = num_retries |
| yield |
| self.num_retries = old_num_retries |
| |
| def ProcessRequest(self, method_config, request): |
| """Hook for pre-processing of requests.""" |
| if self.log_request: |
| logging.info( |
| 'Calling method %s with %s: %s', method_config.method_id, |
| method_config.request_type_name, request) |
| return request |
| |
| def ProcessHttpRequest(self, http_request): |
| """Hook for pre-processing of http requests.""" |
| http_request.headers.update(self.additional_http_headers) |
| if self.log_request: |
| logging.info('Making http %s to %s', |
| http_request.http_method, http_request.url) |
| logging.info('Headers: %s', pprint.pformat(http_request.headers)) |
| if http_request.body: |
| # TODO(craigcitro): Make this safe to print in the case of |
| # non-printable body characters. |
| logging.info('Body:\n%s', |
| http_request.loggable_body or http_request.body) |
| else: |
| logging.info('Body: (none)') |
| return http_request |
| |
| def ProcessResponse(self, method_config, response): |
| if self.log_response: |
| logging.info('Response of type %s: %s', |
| method_config.response_type_name, response) |
| return response |
| |
| # TODO(craigcitro): Decide where these two functions should live. |
| def SerializeMessage(self, message): |
| return encoding.MessageToJson( |
| message, include_fields=self.__include_fields) |
| |
| def DeserializeMessage(self, response_type, data): |
| """Deserialize the given data as method_config.response_type.""" |
| try: |
| message = encoding.JsonToMessage(response_type, data) |
| except (exceptions.InvalidDataFromServerError, |
| messages.ValidationError, ValueError) as e: |
| raise exceptions.InvalidDataFromServerError( |
| 'Error decoding response "%s" as type %s: %s' % ( |
| data, response_type.__name__, e)) |
| return message |
| |
| def FinalizeTransferUrl(self, url): |
| """Modify the url for a given transfer, based on auth and version.""" |
| url_builder = _UrlBuilder.FromUrl(url) |
| if self.global_params.key: |
| url_builder.query_params['key'] = self.global_params.key |
| return url_builder.url |
| |
| |
| class BaseApiService(object): |
| |
| """Base class for generated API services.""" |
| |
| def __init__(self, client): |
| self.__client = client |
| self._method_configs = {} |
| self._upload_configs = {} |
| |
| @property |
| def _client(self): |
| return self.__client |
| |
| @property |
| def client(self): |
| return self.__client |
| |
| def GetMethodConfig(self, method): |
| """Returns service cached method config for given method.""" |
| method_config = self._method_configs.get(method) |
| if method_config: |
| return method_config |
| func = getattr(self, method, None) |
| if func is None: |
| raise KeyError(method) |
| method_config = getattr(func, 'method_config', None) |
| if method_config is None: |
| raise KeyError(method) |
| self._method_configs[method] = config = method_config() |
| return config |
| |
| @classmethod |
| def GetMethodsList(cls): |
| return [f.__name__ for f in six.itervalues(cls.__dict__) |
| if getattr(f, 'method_config', None)] |
| |
| def GetUploadConfig(self, method): |
| return self._upload_configs.get(method) |
| |
| def GetRequestType(self, method): |
| method_config = self.GetMethodConfig(method) |
| return getattr(self.client.MESSAGES_MODULE, |
| method_config.request_type_name) |
| |
| def GetResponseType(self, method): |
| method_config = self.GetMethodConfig(method) |
| return getattr(self.client.MESSAGES_MODULE, |
| method_config.response_type_name) |
| |
| def __CombineGlobalParams(self, global_params, default_params): |
| """Combine the given params with the defaults.""" |
| util.Typecheck(global_params, (type(None), self.__client.params_type)) |
| result = self.__client.params_type() |
| global_params = global_params or self.__client.params_type() |
| for field in result.all_fields(): |
| value = global_params.get_assigned_value(field.name) |
| if value is None: |
| value = default_params.get_assigned_value(field.name) |
| if value not in (None, [], ()): |
| setattr(result, field.name, value) |
| return result |
| |
| def __EncodePrettyPrint(self, query_info): |
| # The prettyPrint flag needs custom encoding: it should be encoded |
| # as 0 if False, and ignored otherwise (True is the default). |
| if not query_info.pop('prettyPrint', True): |
| query_info['prettyPrint'] = 0 |
| # The One Platform equivalent of prettyPrint is pp, which also needs |
| # custom encoding. |
| if not query_info.pop('pp', True): |
| query_info['pp'] = 0 |
| return query_info |
| |
| def __FinalUrlValue(self, value, field): |
| """Encode value for the URL, using field to skip encoding for bytes.""" |
| if isinstance(field, messages.BytesField) and value is not None: |
| return base64.urlsafe_b64encode(value) |
| elif isinstance(value, six.text_type): |
| return value.encode('utf8') |
| elif isinstance(value, six.binary_type): |
| return value.decode('utf8') |
| elif isinstance(value, datetime.datetime): |
| return value.isoformat() |
| return value |
| |
| def __ConstructQueryParams(self, query_params, request, global_params): |
| """Construct a dictionary of query parameters for this request.""" |
| # First, handle the global params. |
| global_params = self.__CombineGlobalParams( |
| global_params, self.__client.global_params) |
| global_param_names = util.MapParamNames( |
| [x.name for x in self.__client.params_type.all_fields()], |
| self.__client.params_type) |
| global_params_type = type(global_params) |
| query_info = dict( |
| (param, |
| self.__FinalUrlValue(getattr(global_params, param), |
| getattr(global_params_type, param))) |
| for param in global_param_names) |
| # Next, add the query params. |
| query_param_names = util.MapParamNames(query_params, type(request)) |
| request_type = type(request) |
| query_info.update( |
| (param, |
| self.__FinalUrlValue(getattr(request, param, None), |
| getattr(request_type, param))) |
| for param in query_param_names) |
| query_info = dict((k, v) for k, v in query_info.items() |
| if v is not None) |
| query_info = self.__EncodePrettyPrint(query_info) |
| query_info = util.MapRequestParams(query_info, type(request)) |
| return query_info |
| |
| def __ConstructRelativePath(self, method_config, request, |
| relative_path=None): |
| """Determine the relative path for request.""" |
| python_param_names = util.MapParamNames( |
| method_config.path_params, type(request)) |
| params = dict([(param, getattr(request, param, None)) |
| for param in python_param_names]) |
| params = util.MapRequestParams(params, type(request)) |
| return util.ExpandRelativePath(method_config, params, |
| relative_path=relative_path) |
| |
| def __FinalizeRequest(self, http_request, url_builder): |
| """Make any final general adjustments to the request.""" |
| if (http_request.http_method == 'GET' and |
| len(http_request.url) > _MAX_URL_LENGTH): |
| http_request.http_method = 'POST' |
| http_request.headers['x-http-method-override'] = 'GET' |
| http_request.headers[ |
| 'content-type'] = 'application/x-www-form-urlencoded' |
| http_request.body = url_builder.query |
| url_builder.query_params = {} |
| http_request.url = url_builder.url |
| |
| def __ProcessHttpResponse(self, method_config, http_response, request): |
| """Process the given http response.""" |
| if http_response.status_code not in (http_client.OK, |
| http_client.NO_CONTENT): |
| raise exceptions.HttpError( |
| http_response.info, http_response.content, |
| http_response.request_url, method_config, request) |
| if http_response.status_code == http_client.NO_CONTENT: |
| # TODO(craigcitro): Find out why _replace doesn't seem to work |
| # here. |
| http_response = http_wrapper.Response( |
| info=http_response.info, content='{}', |
| request_url=http_response.request_url) |
| if self.__client.response_type_model == 'json': |
| return http_response.content |
| response_type = _LoadClass(method_config.response_type_name, |
| self.__client.MESSAGES_MODULE) |
| return self.__client.DeserializeMessage( |
| response_type, http_response.content) |
| |
| def __SetBaseHeaders(self, http_request, client): |
| """Fill in the basic headers on http_request.""" |
| # TODO(craigcitro): Make the default a little better here, and |
| # include the apitools version. |
| user_agent = client.user_agent or 'apitools-client/1.0' |
| http_request.headers['user-agent'] = user_agent |
| http_request.headers['accept'] = 'application/json' |
| http_request.headers['accept-encoding'] = 'gzip, deflate' |
| |
| def __SetBody(self, http_request, method_config, request, upload): |
| """Fill in the body on http_request.""" |
| if not method_config.request_field: |
| return |
| |
| request_type = _LoadClass( |
| method_config.request_type_name, self.__client.MESSAGES_MODULE) |
| if method_config.request_field == REQUEST_IS_BODY: |
| body_value = request |
| body_type = request_type |
| else: |
| body_value = getattr(request, method_config.request_field) |
| body_field = request_type.field_by_name( |
| method_config.request_field) |
| util.Typecheck(body_field, messages.MessageField) |
| body_type = body_field.type |
| |
| # If there was no body provided, we use an empty message of the |
| # appropriate type. |
| body_value = body_value or body_type() |
| if upload and not body_value: |
| # We're going to fill in the body later. |
| return |
| util.Typecheck(body_value, body_type) |
| http_request.headers['content-type'] = 'application/json' |
| http_request.body = self.__client.SerializeMessage(body_value) |
| |
| def PrepareHttpRequest(self, method_config, request, global_params=None, |
| upload=None, upload_config=None, download=None): |
| """Prepares an HTTP request to be sent.""" |
| request_type = _LoadClass( |
| method_config.request_type_name, self.__client.MESSAGES_MODULE) |
| util.Typecheck(request, request_type) |
| request = self.__client.ProcessRequest(method_config, request) |
| |
| http_request = http_wrapper.Request( |
| http_method=method_config.http_method) |
| self.__SetBaseHeaders(http_request, self.__client) |
| self.__SetBody(http_request, method_config, request, upload) |
| |
| url_builder = _UrlBuilder( |
| self.__client.url, relative_path=method_config.relative_path) |
| url_builder.query_params = self.__ConstructQueryParams( |
| method_config.query_params, request, global_params) |
| |
| # It's important that upload and download go before we fill in the |
| # relative path, so that they can replace it. |
| if upload is not None: |
| upload.ConfigureRequest(upload_config, http_request, url_builder) |
| if download is not None: |
| download.ConfigureRequest(http_request, url_builder) |
| |
| url_builder.relative_path = self.__ConstructRelativePath( |
| method_config, request, relative_path=url_builder.relative_path) |
| self.__FinalizeRequest(http_request, url_builder) |
| |
| return self.__client.ProcessHttpRequest(http_request) |
| |
| def _RunMethod(self, method_config, request, global_params=None, |
| upload=None, upload_config=None, download=None): |
| """Call this method with request.""" |
| if upload is not None and download is not None: |
| # TODO(craigcitro): This just involves refactoring the logic |
| # below into callbacks that we can pass around; in particular, |
| # the order should be that the upload gets the initial request, |
| # and then passes its reply to a download if one exists, and |
| # then that goes to ProcessResponse and is returned. |
| raise exceptions.NotYetImplementedError( |
| 'Cannot yet use both upload and download at once') |
| |
| http_request = self.PrepareHttpRequest( |
| method_config, request, global_params, upload, upload_config, |
| download) |
| |
| # TODO(craigcitro): Make num_retries customizable on Transfer |
| # objects, and pass in self.__client.num_retries when initializing |
| # an upload or download. |
| if download is not None: |
| download.InitializeDownload(http_request, client=self.client) |
| return |
| |
| http_response = None |
| if upload is not None: |
| http_response = upload.InitializeUpload( |
| http_request, client=self.client) |
| if http_response is None: |
| http = self.__client.http |
| if upload and upload.bytes_http: |
| http = upload.bytes_http |
| opts = { |
| 'retries': self.__client.num_retries, |
| 'max_retry_wait': self.__client.max_retry_wait, |
| } |
| if self.__client.check_response_func: |
| opts['check_response_func'] = self.__client.check_response_func |
| if self.__client.retry_func: |
| opts['retry_func'] = self.__client.retry_func |
| http_response = http_wrapper.MakeRequest( |
| http, http_request, **opts) |
| |
| return self.ProcessHttpResponse(method_config, http_response, request) |
| |
| def ProcessHttpResponse(self, method_config, http_response, request=None): |
| """Convert an HTTP response to the expected message type.""" |
| return self.__client.ProcessResponse( |
| method_config, |
| self.__ProcessHttpResponse(method_config, http_response, request)) |