Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(76)

Side by Side Diff: third_party/google-endpoints/apitools/base/py/base_api.py

Issue 2666783008: Add google-endpoints to third_party/. (Closed)
Patch Set: Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 #
3 # Copyright 2015 Google Inc.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 """Base class for api services."""
18
19 import base64
20 import contextlib
21 import datetime
22 import logging
23 import pprint
24
25
26 import six
27 from six.moves import http_client
28 from six.moves import urllib
29
30
31 from apitools.base.protorpclite import message_types
32 from apitools.base.protorpclite import messages
33 from apitools.base.py import encoding
34 from apitools.base.py import exceptions
35 from apitools.base.py import http_wrapper
36 from apitools.base.py import util
37
38 __all__ = [
39 'ApiMethodInfo',
40 'ApiUploadInfo',
41 'BaseApiClient',
42 'BaseApiService',
43 'NormalizeApiEndpoint',
44 ]
45
46 # TODO(craigcitro): Remove this once we quiet the spurious logging in
47 # oauth2client (or drop oauth2client).
48 logging.getLogger('oauth2client.util').setLevel(logging.ERROR)
49
50 _MAX_URL_LENGTH = 2048
51
52
53 class ApiUploadInfo(messages.Message):
54
55 """Media upload information for a method.
56
57 Fields:
58 accept: (repeated) MIME Media Ranges for acceptable media uploads
59 to this method.
60 max_size: (integer) Maximum size of a media upload, such as 3MB
61 or 1TB (converted to an integer).
62 resumable_path: Path to use for resumable uploads.
63 resumable_multipart: (boolean) Whether or not the resumable endpoint
64 supports multipart uploads.
65 simple_path: Path to use for simple uploads.
66 simple_multipart: (boolean) Whether or not the simple endpoint
67 supports multipart uploads.
68 """
69 accept = messages.StringField(1, repeated=True)
70 max_size = messages.IntegerField(2)
71 resumable_path = messages.StringField(3)
72 resumable_multipart = messages.BooleanField(4)
73 simple_path = messages.StringField(5)
74 simple_multipart = messages.BooleanField(6)
75
76
77 class ApiMethodInfo(messages.Message):
78
79 """Configuration info for an API method.
80
81 All fields are strings unless noted otherwise.
82
83 Fields:
84 relative_path: Relative path for this method.
85 method_id: ID for this method.
86 http_method: HTTP verb to use for this method.
87 path_params: (repeated) path parameters for this method.
88 query_params: (repeated) query parameters for this method.
89 ordered_params: (repeated) ordered list of parameters for
90 this method.
91 description: description of this method.
92 request_type_name: name of the request type.
93 response_type_name: name of the response type.
94 request_field: if not null, the field to pass as the body
95 of this POST request. may also be the REQUEST_IS_BODY
96 value below to indicate the whole message is the body.
97 upload_config: (ApiUploadInfo) Information about the upload
98 configuration supported by this method.
99 supports_download: (boolean) If True, this method supports
100 downloading the request via the `alt=media` query
101 parameter.
102 """
103
104 relative_path = messages.StringField(1)
105 method_id = messages.StringField(2)
106 http_method = messages.StringField(3)
107 path_params = messages.StringField(4, repeated=True)
108 query_params = messages.StringField(5, repeated=True)
109 ordered_params = messages.StringField(6, repeated=True)
110 description = messages.StringField(7)
111 request_type_name = messages.StringField(8)
112 response_type_name = messages.StringField(9)
113 request_field = messages.StringField(10, default='')
114 upload_config = messages.MessageField(ApiUploadInfo, 11)
115 supports_download = messages.BooleanField(12, default=False)
116 REQUEST_IS_BODY = '<request>'
117
118
119 def _LoadClass(name, messages_module):
120 if name.startswith('message_types.'):
121 _, _, classname = name.partition('.')
122 return getattr(message_types, classname)
123 elif '.' not in name:
124 return getattr(messages_module, name)
125 else:
126 raise exceptions.GeneratedClientError('Unknown class %s' % name)
127
128
129 def _RequireClassAttrs(obj, attrs):
130 for attr in attrs:
131 attr_name = attr.upper()
132 if not hasattr(obj, '%s' % attr_name) or not getattr(obj, attr_name):
133 msg = 'No %s specified for object of class %s.' % (
134 attr_name, type(obj).__name__)
135 raise exceptions.GeneratedClientError(msg)
136
137
138 def NormalizeApiEndpoint(api_endpoint):
139 if not api_endpoint.endswith('/'):
140 api_endpoint += '/'
141 return api_endpoint
142
143
144 def _urljoin(base, url): # pylint: disable=invalid-name
145 """Custom urljoin replacement supporting : before / in url."""
146 # In general, it's unsafe to simply join base and url. However, for
147 # the case of discovery documents, we know:
148 # * base will never contain params, query, or fragment
149 # * url will never contain a scheme or net_loc.
150 # In general, this means we can safely join on /; we just need to
151 # ensure we end up with precisely one / joining base and url. The
152 # exception here is the case of media uploads, where url will be an
153 # absolute url.
154 if url.startswith('http://') or url.startswith('https://'):
155 return urllib.parse.urljoin(base, url)
156 new_base = base if base.endswith('/') else base + '/'
157 new_url = url[1:] if url.startswith('/') else url
158 return new_base + new_url
159
160
161 class _UrlBuilder(object):
162
163 """Convenient container for url data."""
164
165 def __init__(self, base_url, relative_path=None, query_params=None):
166 components = urllib.parse.urlsplit(_urljoin(
167 base_url, relative_path or ''))
168 if components.fragment:
169 raise exceptions.ConfigurationValueError(
170 'Unexpected url fragment: %s' % components.fragment)
171 self.query_params = urllib.parse.parse_qs(components.query or '')
172 if query_params is not None:
173 self.query_params.update(query_params)
174 self.__scheme = components.scheme
175 self.__netloc = components.netloc
176 self.relative_path = components.path or ''
177
178 @classmethod
179 def FromUrl(cls, url):
180 urlparts = urllib.parse.urlsplit(url)
181 query_params = urllib.parse.parse_qs(urlparts.query)
182 base_url = urllib.parse.urlunsplit((
183 urlparts.scheme, urlparts.netloc, '', None, None))
184 relative_path = urlparts.path or ''
185 return cls(
186 base_url, relative_path=relative_path, query_params=query_params)
187
188 @property
189 def base_url(self):
190 return urllib.parse.urlunsplit(
191 (self.__scheme, self.__netloc, '', '', ''))
192
193 @base_url.setter
194 def base_url(self, value):
195 components = urllib.parse.urlsplit(value)
196 if components.path or components.query or components.fragment:
197 raise exceptions.ConfigurationValueError(
198 'Invalid base url: %s' % value)
199 self.__scheme = components.scheme
200 self.__netloc = components.netloc
201
202 @property
203 def query(self):
204 # TODO(craigcitro): In the case that some of the query params are
205 # non-ASCII, we may silently fail to encode correctly. We should
206 # figure out who is responsible for owning the object -> str
207 # conversion.
208 return urllib.parse.urlencode(self.query_params, True)
209
210 @property
211 def url(self):
212 if '{' in self.relative_path or '}' in self.relative_path:
213 raise exceptions.ConfigurationValueError(
214 'Cannot create url with relative path %s' % self.relative_path)
215 return urllib.parse.urlunsplit((
216 self.__scheme, self.__netloc, self.relative_path, self.query, ''))
217
218
219 def _SkipGetCredentials():
220 """Hook for skipping credentials. For internal use."""
221 return False
222
223
224 class BaseApiClient(object):
225
226 """Base class for client libraries."""
227 MESSAGES_MODULE = None
228
229 _API_KEY = ''
230 _CLIENT_ID = ''
231 _CLIENT_SECRET = ''
232 _PACKAGE = ''
233 _SCOPES = []
234 _USER_AGENT = ''
235
236 def __init__(self, url, credentials=None, get_credentials=True, http=None,
237 model=None, log_request=False, log_response=False,
238 num_retries=5, max_retry_wait=60, credentials_args=None,
239 default_global_params=None, additional_http_headers=None,
240 check_response_func=None):
241 _RequireClassAttrs(self, ('_package', '_scopes', 'messages_module'))
242 if default_global_params is not None:
243 util.Typecheck(default_global_params, self.params_type)
244 self.__default_global_params = default_global_params
245 self.log_request = log_request
246 self.log_response = log_response
247 self.__num_retries = 5
248 self.__max_retry_wait = 60
249 # We let the @property machinery below do our validation.
250 self.num_retries = num_retries
251 self.max_retry_wait = max_retry_wait
252 self._credentials = credentials
253 get_credentials = get_credentials and not _SkipGetCredentials()
254 if get_credentials and not credentials:
255 credentials_args = credentials_args or {}
256 self._SetCredentials(**credentials_args)
257 self._url = NormalizeApiEndpoint(url)
258 self._http = http or http_wrapper.GetHttp()
259 # Note that "no credentials" is totally possible.
260 if self._credentials is not None:
261 self._http = self._credentials.authorize(self._http)
262 # TODO(craigcitro): Remove this field when we switch to proto2.
263 self.__include_fields = None
264
265 self.additional_http_headers = additional_http_headers or {}
266 self.__check_response_func = check_response_func
267
268 # TODO(craigcitro): Finish deprecating these fields.
269 _ = model
270
271 self.__response_type_model = 'proto'
272
273 def _SetCredentials(self, **kwds):
274 """Fetch credentials, and set them for this client.
275
276 Note that we can't simply return credentials, since creating them
277 may involve side-effecting self.
278
279 Args:
280 **kwds: Additional keyword arguments are passed on to GetCredentials.
281
282 Returns:
283 None. Sets self._credentials.
284 """
285 args = {
286 'api_key': self._API_KEY,
287 'client': self,
288 'client_id': self._CLIENT_ID,
289 'client_secret': self._CLIENT_SECRET,
290 'package_name': self._PACKAGE,
291 'scopes': self._SCOPES,
292 'user_agent': self._USER_AGENT,
293 }
294 args.update(kwds)
295 # credentials_lib can be expensive to import so do it only if needed.
296 from apitools.base.py import credentials_lib
297 # TODO(craigcitro): It's a bit dangerous to pass this
298 # still-half-initialized self into this method, but we might need
299 # to set attributes on it associated with our credentials.
300 # Consider another way around this (maybe a callback?) and whether
301 # or not it's worth it.
302 self._credentials = credentials_lib.GetCredentials(**args)
303
304 @classmethod
305 def ClientInfo(cls):
306 return {
307 'client_id': cls._CLIENT_ID,
308 'client_secret': cls._CLIENT_SECRET,
309 'scope': ' '.join(sorted(util.NormalizeScopes(cls._SCOPES))),
310 'user_agent': cls._USER_AGENT,
311 }
312
313 @property
314 def base_model_class(self):
315 return None
316
317 @property
318 def http(self):
319 return self._http
320
321 @property
322 def url(self):
323 return self._url
324
325 @classmethod
326 def GetScopes(cls):
327 return cls._SCOPES
328
329 @property
330 def params_type(self):
331 return _LoadClass('StandardQueryParameters', self.MESSAGES_MODULE)
332
333 @property
334 def user_agent(self):
335 return self._USER_AGENT
336
337 @property
338 def _default_global_params(self):
339 if self.__default_global_params is None:
340 self.__default_global_params = self.params_type()
341 return self.__default_global_params
342
343 def AddGlobalParam(self, name, value):
344 params = self._default_global_params
345 setattr(params, name, value)
346
347 @property
348 def global_params(self):
349 return encoding.CopyProtoMessage(self._default_global_params)
350
351 @contextlib.contextmanager
352 def IncludeFields(self, include_fields):
353 self.__include_fields = include_fields
354 yield
355 self.__include_fields = None
356
357 @property
358 def response_type_model(self):
359 return self.__response_type_model
360
361 @contextlib.contextmanager
362 def JsonResponseModel(self):
363 """In this context, return raw JSON instead of proto."""
364 old_model = self.response_type_model
365 self.__response_type_model = 'json'
366 yield
367 self.__response_type_model = old_model
368
369 @property
370 def num_retries(self):
371 return self.__num_retries
372
373 @num_retries.setter
374 def num_retries(self, value):
375 util.Typecheck(value, six.integer_types)
376 if value < 0:
377 raise exceptions.InvalidDataError(
378 'Cannot have negative value for num_retries')
379 self.__num_retries = value
380
381 @property
382 def check_response_func(self):
383 return self.__check_response_func
384
385 @check_response_func.setter
386 def check_response_func(self, value):
387 self.__check_response_func = value
388
389 @property
390 def max_retry_wait(self):
391 return self.__max_retry_wait
392
393 @max_retry_wait.setter
394 def max_retry_wait(self, value):
395 util.Typecheck(value, six.integer_types)
396 if value <= 0:
397 raise exceptions.InvalidDataError(
398 'max_retry_wait must be a postiive integer')
399 self.__max_retry_wait = value
400
401 @contextlib.contextmanager
402 def WithRetries(self, num_retries):
403 old_num_retries = self.num_retries
404 self.num_retries = num_retries
405 yield
406 self.num_retries = old_num_retries
407
408 def ProcessRequest(self, method_config, request):
409 """Hook for pre-processing of requests."""
410 if self.log_request:
411 logging.info(
412 'Calling method %s with %s: %s', method_config.method_id,
413 method_config.request_type_name, request)
414 return request
415
416 def ProcessHttpRequest(self, http_request):
417 """Hook for pre-processing of http requests."""
418 http_request.headers.update(self.additional_http_headers)
419 if self.log_request:
420 logging.info('Making http %s to %s',
421 http_request.http_method, http_request.url)
422 logging.info('Headers: %s', pprint.pformat(http_request.headers))
423 if http_request.body:
424 # TODO(craigcitro): Make this safe to print in the case of
425 # non-printable body characters.
426 logging.info('Body:\n%s',
427 http_request.loggable_body or http_request.body)
428 else:
429 logging.info('Body: (none)')
430 return http_request
431
432 def ProcessResponse(self, method_config, response):
433 if self.log_response:
434 logging.info('Response of type %s: %s',
435 method_config.response_type_name, response)
436 return response
437
438 # TODO(craigcitro): Decide where these two functions should live.
439 def SerializeMessage(self, message):
440 return encoding.MessageToJson(
441 message, include_fields=self.__include_fields)
442
443 def DeserializeMessage(self, response_type, data):
444 """Deserialize the given data as method_config.response_type."""
445 try:
446 message = encoding.JsonToMessage(response_type, data)
447 except (exceptions.InvalidDataFromServerError,
448 messages.ValidationError, ValueError) as e:
449 raise exceptions.InvalidDataFromServerError(
450 'Error decoding response "%s" as type %s: %s' % (
451 data, response_type.__name__, e))
452 return message
453
454 def FinalizeTransferUrl(self, url):
455 """Modify the url for a given transfer, based on auth and version."""
456 url_builder = _UrlBuilder.FromUrl(url)
457 if self.global_params.key:
458 url_builder.query_params['key'] = self.global_params.key
459 return url_builder.url
460
461
462 class BaseApiService(object):
463
464 """Base class for generated API services."""
465
466 def __init__(self, client):
467 self.__client = client
468 self._method_configs = {}
469 self._upload_configs = {}
470
471 @property
472 def _client(self):
473 return self.__client
474
475 @property
476 def client(self):
477 return self.__client
478
479 def GetMethodConfig(self, method):
480 return self._method_configs[method]
481
482 def GetUploadConfig(self, method):
483 return self._upload_configs.get(method)
484
485 def GetRequestType(self, method):
486 method_config = self.GetMethodConfig(method)
487 return getattr(self.client.MESSAGES_MODULE,
488 method_config.request_type_name)
489
490 def GetResponseType(self, method):
491 method_config = self.GetMethodConfig(method)
492 return getattr(self.client.MESSAGES_MODULE,
493 method_config.response_type_name)
494
495 def __CombineGlobalParams(self, global_params, default_params):
496 """Combine the given params with the defaults."""
497 util.Typecheck(global_params, (type(None), self.__client.params_type))
498 result = self.__client.params_type()
499 global_params = global_params or self.__client.params_type()
500 for field in result.all_fields():
501 value = global_params.get_assigned_value(field.name)
502 if value is None:
503 value = default_params.get_assigned_value(field.name)
504 if value not in (None, [], ()):
505 setattr(result, field.name, value)
506 return result
507
508 def __EncodePrettyPrint(self, query_info):
509 # The prettyPrint flag needs custom encoding: it should be encoded
510 # as 0 if False, and ignored otherwise (True is the default).
511 if not query_info.pop('prettyPrint', True):
512 query_info['prettyPrint'] = 0
513 # The One Platform equivalent of prettyPrint is pp, which also needs
514 # custom encoding.
515 if not query_info.pop('pp', True):
516 query_info['pp'] = 0
517 return query_info
518
519 def __FinalUrlValue(self, value, field):
520 """Encode value for the URL, using field to skip encoding for bytes."""
521 if isinstance(field, messages.BytesField) and value is not None:
522 return base64.urlsafe_b64encode(value)
523 elif isinstance(value, six.text_type):
524 return value.encode('utf8')
525 elif isinstance(value, six.binary_type):
526 return value.decode('utf8')
527 elif isinstance(value, datetime.datetime):
528 return value.isoformat()
529 return value
530
531 def __ConstructQueryParams(self, query_params, request, global_params):
532 """Construct a dictionary of query parameters for this request."""
533 # First, handle the global params.
534 global_params = self.__CombineGlobalParams(
535 global_params, self.__client.global_params)
536 global_param_names = util.MapParamNames(
537 [x.name for x in self.__client.params_type.all_fields()],
538 self.__client.params_type)
539 global_params_type = type(global_params)
540 query_info = dict(
541 (param,
542 self.__FinalUrlValue(getattr(global_params, param),
543 getattr(global_params_type, param)))
544 for param in global_param_names)
545 # Next, add the query params.
546 query_param_names = util.MapParamNames(query_params, type(request))
547 request_type = type(request)
548 query_info.update(
549 (param,
550 self.__FinalUrlValue(getattr(request, param, None),
551 getattr(request_type, param)))
552 for param in query_param_names)
553 query_info = dict((k, v) for k, v in query_info.items()
554 if v is not None)
555 query_info = self.__EncodePrettyPrint(query_info)
556 query_info = util.MapRequestParams(query_info, type(request))
557 return query_info
558
559 def __ConstructRelativePath(self, method_config, request,
560 relative_path=None):
561 """Determine the relative path for request."""
562 python_param_names = util.MapParamNames(
563 method_config.path_params, type(request))
564 params = dict([(param, getattr(request, param, None))
565 for param in python_param_names])
566 params = util.MapRequestParams(params, type(request))
567 return util.ExpandRelativePath(method_config, params,
568 relative_path=relative_path)
569
570 def __FinalizeRequest(self, http_request, url_builder):
571 """Make any final general adjustments to the request."""
572 if (http_request.http_method == 'GET' and
573 len(http_request.url) > _MAX_URL_LENGTH):
574 http_request.http_method = 'POST'
575 http_request.headers['x-http-method-override'] = 'GET'
576 http_request.headers[
577 'content-type'] = 'application/x-www-form-urlencoded'
578 http_request.body = url_builder.query
579 url_builder.query_params = {}
580 http_request.url = url_builder.url
581
582 def __ProcessHttpResponse(self, method_config, http_response):
583 """Process the given http response."""
584 if http_response.status_code not in (http_client.OK,
585 http_client.NO_CONTENT):
586 raise exceptions.HttpError.FromResponse(http_response)
587 if http_response.status_code == http_client.NO_CONTENT:
588 # TODO(craigcitro): Find out why _replace doesn't seem to work
589 # here.
590 http_response = http_wrapper.Response(
591 info=http_response.info, content='{}',
592 request_url=http_response.request_url)
593 if self.__client.response_type_model == 'json':
594 return http_response.content
595 else:
596 response_type = _LoadClass(method_config.response_type_name,
597 self.__client.MESSAGES_MODULE)
598 return self.__client.DeserializeMessage(
599 response_type, http_response.content)
600
601 def __SetBaseHeaders(self, http_request, client):
602 """Fill in the basic headers on http_request."""
603 # TODO(craigcitro): Make the default a little better here, and
604 # include the apitools version.
605 user_agent = client.user_agent or 'apitools-client/1.0'
606 http_request.headers['user-agent'] = user_agent
607 http_request.headers['accept'] = 'application/json'
608 http_request.headers['accept-encoding'] = 'gzip, deflate'
609
610 def __SetBody(self, http_request, method_config, request, upload):
611 """Fill in the body on http_request."""
612 if not method_config.request_field:
613 return
614
615 request_type = _LoadClass(
616 method_config.request_type_name, self.__client.MESSAGES_MODULE)
617 if method_config.request_field == REQUEST_IS_BODY:
618 body_value = request
619 body_type = request_type
620 else:
621 body_value = getattr(request, method_config.request_field)
622 body_field = request_type.field_by_name(
623 method_config.request_field)
624 util.Typecheck(body_field, messages.MessageField)
625 body_type = body_field.type
626
627 # If there was no body provided, we use an empty message of the
628 # appropriate type.
629 body_value = body_value or body_type()
630 if upload and not body_value:
631 # We're going to fill in the body later.
632 return
633 util.Typecheck(body_value, body_type)
634 http_request.headers['content-type'] = 'application/json'
635 http_request.body = self.__client.SerializeMessage(body_value)
636
637 def PrepareHttpRequest(self, method_config, request, global_params=None,
638 upload=None, upload_config=None, download=None):
639 """Prepares an HTTP request to be sent."""
640 request_type = _LoadClass(
641 method_config.request_type_name, self.__client.MESSAGES_MODULE)
642 util.Typecheck(request, request_type)
643 request = self.__client.ProcessRequest(method_config, request)
644
645 http_request = http_wrapper.Request(
646 http_method=method_config.http_method)
647 self.__SetBaseHeaders(http_request, self.__client)
648 self.__SetBody(http_request, method_config, request, upload)
649
650 url_builder = _UrlBuilder(
651 self.__client.url, relative_path=method_config.relative_path)
652 url_builder.query_params = self.__ConstructQueryParams(
653 method_config.query_params, request, global_params)
654
655 # It's important that upload and download go before we fill in the
656 # relative path, so that they can replace it.
657 if upload is not None:
658 upload.ConfigureRequest(upload_config, http_request, url_builder)
659 if download is not None:
660 download.ConfigureRequest(http_request, url_builder)
661
662 url_builder.relative_path = self.__ConstructRelativePath(
663 method_config, request, relative_path=url_builder.relative_path)
664 self.__FinalizeRequest(http_request, url_builder)
665
666 return self.__client.ProcessHttpRequest(http_request)
667
668 def _RunMethod(self, method_config, request, global_params=None,
669 upload=None, upload_config=None, download=None):
670 """Call this method with request."""
671 if upload is not None and download is not None:
672 # TODO(craigcitro): This just involves refactoring the logic
673 # below into callbacks that we can pass around; in particular,
674 # the order should be that the upload gets the initial request,
675 # and then passes its reply to a download if one exists, and
676 # then that goes to ProcessResponse and is returned.
677 raise exceptions.NotYetImplementedError(
678 'Cannot yet use both upload and download at once')
679
680 http_request = self.PrepareHttpRequest(
681 method_config, request, global_params, upload, upload_config,
682 download)
683
684 # TODO(craigcitro): Make num_retries customizable on Transfer
685 # objects, and pass in self.__client.num_retries when initializing
686 # an upload or download.
687 if download is not None:
688 download.InitializeDownload(http_request, client=self.client)
689 return
690
691 http_response = None
692 if upload is not None:
693 http_response = upload.InitializeUpload(
694 http_request, client=self.client)
695 if http_response is None:
696 http = self.__client.http
697 if upload and upload.bytes_http:
698 http = upload.bytes_http
699 opts = {
700 'retries': self.__client.num_retries,
701 'max_retry_wait': self.__client.max_retry_wait,
702 }
703 if self.__client.check_response_func:
704 opts['check_response_func'] = self.__client.check_response_func
705 http_response = http_wrapper.MakeRequest(
706 http, http_request, **opts)
707
708 return self.ProcessHttpResponse(method_config, http_response)
709
710 def ProcessHttpResponse(self, method_config, http_response):
711 """Convert an HTTP response to the expected message type."""
712 return self.__client.ProcessResponse(
713 method_config,
714 self.__ProcessHttpResponse(method_config, http_response))
OLDNEW
« no previous file with comments | « third_party/google-endpoints/apitools/base/py/app2.py ('k') | third_party/google-endpoints/apitools/base/py/base_api_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698