diff options
author | Haibo Huang <hhb@google.com> | 2020-05-15 21:03:09 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-05-15 21:03:09 +0000 |
commit | f7c3bec4e79ea14b5841f88b0ded43ced596b78e (patch) | |
tree | e9aff1d08c13ef099a1691d778416a233a712f38 | |
parent | 8a8d7f89f1762f40fd02df9af9d547a99c3aa6b8 (diff) | |
parent | 70045997c545daa465f9a08afb8602276179b092 (diff) | |
download | apitools-f7c3bec4e79ea14b5841f88b0ded43ced596b78e.tar.gz |
Upgrade python/apitools to v0.5.31 am: f864ef688b am: 9b6a69032d am: 89ea80b037 am: 4044c97a7e am: 70045997c5
Change-Id: Ib88eb589d1ef3ff2847525d19fc88c4157ef55c4
44 files changed, 277 insertions, 118 deletions
diff --git a/.travis.yml b/.travis.yml index 627bf3d..453e56d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ language: python -sudo: false matrix: include: - python: "2.7" @@ -12,8 +11,6 @@ matrix: env: TOX_ENV=py27-oauth2client3 - python: "2.7" env: TOX_ENV=py27-oauth2client4 - - python: "3.4" - env: TOX_ENV=py34-oauth2client4 - python: "3.5" env: TOX_ENV=py35-oauth2client1 - python: "3.5" @@ -9,11 +9,11 @@ third_party { type: GIT value: "https://github.com/google/apitools" } - version: "v0.5.30" + version: "v0.5.31" license_type: NOTICE last_upgrade_date { - year: 2019 - month: 6 - day: 26 + year: 2020 + month: 5 + day: 14 } } diff --git a/apitools/base/protorpclite/descriptor.py b/apitools/base/protorpclite/descriptor.py index add0e4c..70b7ed1 100644 --- a/apitools/base/protorpclite/descriptor.py +++ b/apitools/base/protorpclite/descriptor.py @@ -292,7 +292,7 @@ def describe_enum(enum_definition): enum_descriptor.name = enum_definition.definition_name().split('.')[-1] values = [] - for number in enum_definition.numbers(): + for number in sorted(enum_definition.numbers()): value = enum_definition.lookup_by_number(number) values.append(describe_enum_value(value)) diff --git a/apitools/base/protorpclite/descriptor_test.py b/apitools/base/protorpclite/descriptor_test.py index fc27ec4..5fbed35 100644 --- a/apitools/base/protorpclite/descriptor_test.py +++ b/apitools/base/protorpclite/descriptor_test.py @@ -18,9 +18,9 @@ """Tests for apitools.base.protorpclite.descriptor.""" import platform import types +import unittest import six -import unittest2 from apitools.base.protorpclite import descriptor from apitools.base.protorpclite import message_types @@ -78,8 +78,8 @@ class DescribeEnumTest(test_util.TestCase): described.check_initialized() self.assertEquals(expected, described) - @unittest2.skipIf('PyPy' in platform.python_implementation(), - 'todo: reenable this') + @unittest.skipIf('PyPy' in platform.python_implementation(), + 'todo: reenable this') def testEnumWithItems(self): class EnumWithItems(messages.Enum): A = 3 @@ -512,4 +512,4 @@ class DescriptorLibraryTest(test_util.TestCase): if __name__ == '__main__': - unittest2.main() + unittest.main() diff --git a/apitools/base/protorpclite/test_util.py b/apitools/base/protorpclite/test_util.py index 43345fc..89e3a68 100644 --- a/apitools/base/protorpclite/test_util.py +++ b/apitools/base/protorpclite/test_util.py @@ -33,10 +33,10 @@ import os import re import socket import types +import unittest import six from six.moves import range # pylint: disable=redefined-builtin -import unittest2 as unittest from apitools.base.protorpclite import message_types from apitools.base.protorpclite import messages diff --git a/apitools/base/py/base_api_test.py b/apitools/base/py/base_api_test.py index b00085c..27b1727 100644 --- a/apitools/base/py/base_api_test.py +++ b/apitools/base/py/base_api_test.py @@ -17,11 +17,11 @@ import base64 import datetime import sys import contextlib +import unittest import six from six.moves import http_client from six.moves import urllib_parse -import unittest2 from apitools.base.protorpclite import message_types from apitools.base.protorpclite import messages @@ -96,7 +96,7 @@ class FakeService(base_api.BaseApiService): super(FakeService, self).__init__(client) -class BaseApiTest(unittest2.TestCase): +class BaseApiTest(unittest.TestCase): def __GetFakeClient(self): return FakeClient('', credentials=FakeCredentials()) @@ -331,4 +331,4 @@ class BaseApiTest(unittest2.TestCase): if __name__ == '__main__': - unittest2.main() + unittest.main() diff --git a/apitools/base/py/batch_test.py b/apitools/base/py/batch_test.py index 0574dc6..90cf4fb 100644 --- a/apitools/base/py/batch_test.py +++ b/apitools/base/py/batch_test.py @@ -16,12 +16,12 @@ """Tests for apitools.base.py.batch.""" import textwrap +import unittest import mock from six.moves import http_client from six.moves import range # pylint:disable=redefined-builtin from six.moves.urllib import parse -import unittest2 from apitools.base.py import batch from apitools.base.py import exceptions @@ -69,7 +69,7 @@ class FakeService(object): return http_response -class BatchTest(unittest2.TestCase): +class BatchTest(unittest.TestCase): def assertUrlEqual(self, expected_url, provided_url): diff --git a/apitools/base/py/buffered_stream_test.py b/apitools/base/py/buffered_stream_test.py index 2098fb1..4de231d 100644 --- a/apitools/base/py/buffered_stream_test.py +++ b/apitools/base/py/buffered_stream_test.py @@ -16,15 +16,15 @@ """Tests for buffered_stream.""" import string +import unittest import six -import unittest2 from apitools.base.py import buffered_stream from apitools.base.py import exceptions -class BufferedStreamTest(unittest2.TestCase): +class BufferedStreamTest(unittest.TestCase): def setUp(self): self.stream = six.StringIO(string.ascii_letters) diff --git a/apitools/base/py/compression_test.py b/apitools/base/py/compression_test.py index c8ecdac..9832b31 100644 --- a/apitools/base/py/compression_test.py +++ b/apitools/base/py/compression_test.py @@ -16,14 +16,15 @@ """Tests for compression.""" +import unittest + from apitools.base.py import compression from apitools.base.py import gzip import six -import unittest2 -class CompressionTest(unittest2.TestCase): +class CompressionTest(unittest.TestCase): def setUp(self): # Sample highly compressible data (~50MB). @@ -98,7 +99,7 @@ class CompressionTest(unittest2.TestCase): self.assertTrue(exhausted) -class StreamingBufferTest(unittest2.TestCase): +class StreamingBufferTest(unittest.TestCase): def setUp(self): self.stream = compression.StreamingBuffer() diff --git a/apitools/base/py/credentials_lib.py b/apitools/base/py/credentials_lib.py index bf39285..0823f93 100644 --- a/apitools/base/py/credentials_lib.py +++ b/apitools/base/py/credentials_lib.py @@ -17,6 +17,7 @@ """Common credentials classes and constructors.""" from __future__ import print_function +import argparse import contextlib import datetime import json @@ -515,10 +516,6 @@ def _GetRunFlowFlags(args=None): # since they're bringing their own credentials. So we just allow this # to fail with an ImportError in those cases. # - # TODO(craigcitro): Move this import back to the top when we drop - # python 2.6 support (eg when gsutil does). - import argparse - parser = argparse.ArgumentParser(parents=[tools.argparser]) # Get command line argparse flags. flags, _ = parser.parse_known_args(args=args) @@ -721,10 +718,6 @@ def _GetServiceAccountCredentials( client_info, service_account_name=None, service_account_keyfile=None, service_account_json_keyfile=None, **unused_kwds): """Returns ServiceAccountCredentials from give file.""" - if ((service_account_name and not service_account_keyfile) or - (service_account_keyfile and not service_account_name)): - raise exceptions.CredentialsError( - 'Service account name or keyfile provided without the other') scopes = client_info['scope'].split() user_agent = client_info['user_agent'] # Use the .json credentials, if provided. @@ -732,6 +725,10 @@ def _GetServiceAccountCredentials( return ServiceAccountCredentialsFromFile( service_account_json_keyfile, scopes, user_agent=user_agent) # Fall back to .p12 if there's no .json credentials. + if ((service_account_name and not service_account_keyfile) or + (service_account_keyfile and not service_account_name)): + raise exceptions.CredentialsError( + 'Service account name or keyfile provided without the other') if service_account_name is not None: return ServiceAccountCredentialsFromP12File( service_account_name, service_account_keyfile, scopes, user_agent) diff --git a/apitools/base/py/credentials_lib_test.py b/apitools/base/py/credentials_lib_test.py index 80b970c..64f056d 100644 --- a/apitools/base/py/credentials_lib_test.py +++ b/apitools/base/py/credentials_lib_test.py @@ -17,10 +17,10 @@ import json import os.path import shutil import tempfile +import unittest import mock import six -import unittest2 from apitools.base.py import credentials_lib from apitools.base.py import util @@ -43,7 +43,7 @@ class MetadataMock(object): self.fail('Unexpected HTTP request to %s' % request_url) -class CredentialsLibTest(unittest2.TestCase): +class CredentialsLibTest(unittest.TestCase): def _RunGceAssertionCredentials( self, service_account_name=None, scopes=None, cache_filename=None): @@ -153,7 +153,7 @@ class CredentialsLibTest(unittest2.TestCase): self.assertIsNone(creds) -class TestGetRunFlowFlags(unittest2.TestCase): +class TestGetRunFlowFlags(unittest.TestCase): def setUp(self): self._flags_actual = credentials_lib.FLAGS diff --git a/apitools/base/py/encoding_test.py b/apitools/base/py/encoding_test.py index d130cc5..54058a2 100644 --- a/apitools/base/py/encoding_test.py +++ b/apitools/base/py/encoding_test.py @@ -17,8 +17,7 @@ import base64 import datetime import json import sys - -import unittest2 +import unittest from apitools.base.protorpclite import message_types from apitools.base.protorpclite import messages @@ -238,7 +237,7 @@ encoding.AddCustomJsonFieldMapping(MessageWithRemappings, 'repeated_field', 'repeatedField') -class EncodingTest(unittest2.TestCase): +class EncodingTest(unittest.TestCase): def testCopyProtoMessage(self): msg = SimpleMessage(field='abc') diff --git a/apitools/base/py/exceptions_test.py b/apitools/base/py/exceptions_test.py index 4937f73..6e3a182 100644 --- a/apitools/base/py/exceptions_test.py +++ b/apitools/base/py/exceptions_test.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest2 +import unittest from apitools.base.py import exceptions from apitools.base.py import http_wrapper @@ -24,7 +24,7 @@ def _MakeResponse(status_code): request_url='http://www.google.com') -class HttpErrorFromResponseTest(unittest2.TestCase): +class HttpErrorFromResponseTest(unittest.TestCase): """Tests for exceptions.HttpError.FromResponse.""" diff --git a/apitools/base/py/extra_types.py b/apitools/base/py/extra_types.py index 847dc91..e40a785 100644 --- a/apitools/base/py/extra_types.py +++ b/apitools/base/py/extra_types.py @@ -16,7 +16,6 @@ """Extra types understood by apitools.""" -import collections import datetime import json import numbers @@ -30,6 +29,11 @@ from apitools.base.py import encoding_helper as encoding from apitools.base.py import exceptions from apitools.base.py import util +if six.PY3: + from collections.abc import Iterable +else: + from collections import Iterable + __all__ = [ 'DateField', 'DateTimeMessage', @@ -129,7 +133,7 @@ def _PythonValueToJsonValue(py_value): return JsonValue(double_value=float(py_value)) if isinstance(py_value, dict): return JsonValue(object_value=_PythonValueToJsonObject(py_value)) - if isinstance(py_value, collections.Iterable): + if isinstance(py_value, Iterable): return JsonValue(array_value=_PythonValueToJsonArray(py_value)) raise exceptions.InvalidDataError( 'Cannot convert "%s" to JsonValue' % py_value) @@ -212,7 +216,7 @@ def _JsonProtoToPythonValue(json_proto): def _PythonValueToJsonProto(py_value): if isinstance(py_value, dict): return _PythonValueToJsonObject(py_value) - if (isinstance(py_value, collections.Iterable) and + if (isinstance(py_value, Iterable) and not isinstance(py_value, six.string_types)): return _PythonValueToJsonArray(py_value) return _PythonValueToJsonValue(py_value) diff --git a/apitools/base/py/extra_types_test.py b/apitools/base/py/extra_types_test.py index 7e37f7c..6a4092b 100644 --- a/apitools/base/py/extra_types_test.py +++ b/apitools/base/py/extra_types_test.py @@ -16,8 +16,7 @@ import datetime import json import math - -import unittest2 +import unittest from apitools.base.protorpclite import messages from apitools.base.py import encoding @@ -25,7 +24,7 @@ from apitools.base.py import exceptions from apitools.base.py import extra_types -class ExtraTypesTest(unittest2.TestCase): +class ExtraTypesTest(unittest.TestCase): def assertRoundTrip(self, value): if isinstance(value, extra_types._JSON_PROTO_TYPES): diff --git a/apitools/base/py/http_wrapper.py b/apitools/base/py/http_wrapper.py index a3fe65c..c31bea0 100644 --- a/apitools/base/py/http_wrapper.py +++ b/apitools/base/py/http_wrapper.py @@ -339,6 +339,10 @@ def MakeRequest(http, http_request, retries=7, max_retry_wait=60, """ retry = 0 first_req_time = time.time() + # Provide compatibility for breaking change in httplib2 0.16.0+: + # https://github.com/googleapis/google-api-python-client/issues/803 + if hasattr(http, 'redirect_codes'): + http.redirect_codes = set(http.redirect_codes) - {308} while True: try: return _MakeRequestNoRetry( diff --git a/apitools/base/py/http_wrapper_test.py b/apitools/base/py/http_wrapper_test.py index ce4c03e..fdf56f5 100644 --- a/apitools/base/py/http_wrapper_test.py +++ b/apitools/base/py/http_wrapper_test.py @@ -15,10 +15,10 @@ """Tests for http_wrapper.""" import socket +import unittest import httplib2 from six.moves import http_client -import unittest2 from mock import patch @@ -57,7 +57,7 @@ class RaisesExceptionOnLen(object): return 1 -class HttpWrapperTest(unittest2.TestCase): +class HttpWrapperTest(unittest.TestCase): def testRequestBodyUsesLengthProperty(self): http_wrapper.Request(body=RaisesExceptionOnLen()) @@ -65,8 +65,8 @@ class HttpWrapperTest(unittest2.TestCase): def testRequestBodyWithLen(self): http_wrapper.Request(body='burrito') - @unittest2.skipIf(not _TOKEN_REFRESH_STATUS_AVAILABLE, - 'oauth2client<1.5 lacks HttpAccessTokenRefreshError.') + @unittest.skipIf(not _TOKEN_REFRESH_STATUS_AVAILABLE, + 'oauth2client<1.5 lacks HttpAccessTokenRefreshError.') def testExceptionHandlerHttpAccessTokenError(self): exception_arg = HttpAccessTokenRefreshError(status=503) retry_args = http_wrapper.ExceptionRetryArgs( @@ -80,8 +80,8 @@ class HttpWrapperTest(unittest2.TestCase): http_wrapper.HandleExceptionsAndRebuildHttpConnections( retry_args) - @unittest2.skipIf(not _TOKEN_REFRESH_STATUS_AVAILABLE, - 'oauth2client<1.5 lacks HttpAccessTokenRefreshError.') + @unittest.skipIf(not _TOKEN_REFRESH_STATUS_AVAILABLE, + 'oauth2client<1.5 lacks HttpAccessTokenRefreshError.') def testExceptionHandlerHttpAccessTokenErrorRaises(self): exception_arg = HttpAccessTokenRefreshError(status=200) retry_args = http_wrapper.ExceptionRetryArgs( diff --git a/apitools/base/py/list_pager.py b/apitools/base/py/list_pager.py index fb14c14..a2c1080 100644 --- a/apitools/base/py/list_pager.py +++ b/apitools/base/py/list_pager.py @@ -17,18 +17,58 @@ """A helper function that executes a series of List queries for many APIs.""" from apitools.base.py import encoding +import six __all__ = [ 'YieldFromList', ] +def _GetattrNested(message, attribute): + """Gets a possibly nested attribute. + + Same as getattr() if attribute is a string; + if attribute is a tuple, returns the nested attribute referred to by + the fields in the tuple as if they were a dotted accessor path. + + (ex _GetattrNested(msg, ('foo', 'bar', 'baz')) gets msg.foo.bar.baz + """ + if isinstance(attribute, six.string_types): + return getattr(message, attribute) + elif len(attribute) == 0: + return message + else: + return _GetattrNested(getattr(message, attribute[0]), attribute[1:]) + + +def _SetattrNested(message, attribute, value): + """Sets a possibly nested attribute. + + Same as setattr() if attribute is a string; + if attribute is a tuple, sets the nested attribute referred to by + the fields in the tuple as if they were a dotted accessor path. + + (ex _SetattrNested(msg, ('foo', 'bar', 'baz'), 'v') sets msg.foo.bar.baz + to 'v' + """ + if isinstance(attribute, six.string_types): + return setattr(message, attribute, value) + elif len(attribute) < 1: + raise ValueError("Need an attribute to set") + elif len(attribute) == 1: + return setattr(message, attribute[0], value) + else: + return setattr(_GetattrNested(message, attribute[:-1]), + attribute[-1], value) + + def YieldFromList( service, request, global_params=None, limit=None, batch_size=100, method='List', field='items', predicate=None, current_token_attribute='pageToken', next_token_attribute='nextPageToken', - batch_size_attribute='maxResults'): + batch_size_attribute='maxResults', + get_field_func=_GetattrNested): """Make a series of List requests, keeping track of page tokens. Args: @@ -45,21 +85,25 @@ def YieldFromList( method: str, The name of the method used to fetch resources. field: str, The field in the response that will be a list of items. predicate: lambda, A function that returns true for items to be yielded. - current_token_attribute: str, The name of the attribute in a + current_token_attribute: str or tuple, The name of the attribute in a request message holding the page token for the page being - requested. - next_token_attribute: str, The name of the attribute in a - response message holding the page token for the next page. - batch_size_attribute: str, The name of the attribute in a + requested. If a tuple, path to attribute. + next_token_attribute: str or tuple, The name of the attribute in a + response message holding the page token for the next page. If a + tuple, path to the attribute. + batch_size_attribute: str or tuple, The name of the attribute in a response message holding the maximum number of results to be returned. None if caller-specified batch size is unsupported. + If a tuple, path to the attribute. + get_field_func: Function that returns the items to be yielded. Argument + is response message, and field. Yields: protorpc.message.Message, The resources listed by the service. """ request = encoding.CopyProtoMessage(request) - setattr(request, current_token_attribute, None) + _SetattrNested(request, current_token_attribute, None) while limit is None or limit: if batch_size_attribute: # On Py3, None is not comparable so min() below will fail. @@ -72,10 +116,10 @@ def YieldFromList( request_batch_size = None else: request_batch_size = min(batch_size, limit or batch_size) - setattr(request, batch_size_attribute, request_batch_size) + _SetattrNested(request, batch_size_attribute, request_batch_size) response = getattr(service, method)(request, global_params=global_params) - items = getattr(response, field) + items = get_field_func(response, field) if predicate: items = list(filter(predicate, items)) for item in items: @@ -85,7 +129,7 @@ def YieldFromList( limit -= 1 if not limit: return - token = getattr(response, next_token_attribute) + token = _GetattrNested(response, next_token_attribute) if not token: return - setattr(request, current_token_attribute, token) + _SetattrNested(request, current_token_attribute, token) diff --git a/apitools/base/py/list_pager_test.py b/apitools/base/py/list_pager_test.py index 32dfea6..1ea6368 100644 --- a/apitools/base/py/list_pager_test.py +++ b/apitools/base/py/list_pager_test.py @@ -15,7 +15,7 @@ """Tests for list_pager.""" -import unittest2 +import unittest from apitools.base.py import list_pager from apitools.base.py.testing import mock @@ -27,7 +27,33 @@ from samples.iam_sample.iam_v1 import iam_v1_client as iam_client from samples.iam_sample.iam_v1 import iam_v1_messages as iam_messages -class ListPagerTest(unittest2.TestCase): +class Example(object): + def __init__(self): + self.a = 'aaa' + self.b = 'bbb' + self.c = 'ccc' + + +class GetterSetterTest(unittest.TestCase): + + def testGetattrNested(self): + o = Example() + self.assertEqual(list_pager._GetattrNested(o, 'a'), 'aaa') + self.assertEqual(list_pager._GetattrNested(o, ('a',)), 'aaa') + o.b = Example() + self.assertEqual(list_pager._GetattrNested(o, ('b', 'c')), 'ccc') + + def testSetattrNested(self): + o = Example() + list_pager._SetattrNested(o, 'b', Example()) + self.assertEqual(o.b.a, 'aaa') + list_pager._SetattrNested(o, ('b', 'a'), 'AAA') + self.assertEqual(o.b.a, 'AAA') + list_pager._SetattrNested(o, ('c',), 'CCC') + self.assertEqual(o.c, 'CCC') + + +class ListPagerTest(unittest.TestCase): def _AssertInstanceSequence(self, results, n): counter = 0 @@ -242,8 +268,34 @@ class ListPagerTest(unittest2.TestCase): self._AssertInstanceSequence(results, 3) + def testYieldFromListWithCustomGetFieldFunction(self): + self.mocked_client.column.List.Expect( + messages.FusiontablesColumnListRequest( + maxResults=100, + pageToken=None, + tableId='mytable', + ), + messages.ColumnList( + items=[ + messages.Column(name='c0') + ] + )) + custom_getter_called = [] + + def Custom_Getter(message, attribute): + custom_getter_called.append(True) + return getattr(message, attribute) + + client = fusiontables.FusiontablesV1(get_credentials=False) + request = messages.FusiontablesColumnListRequest(tableId='mytable') + results = list_pager.YieldFromList( + client.column, request, get_field_func=Custom_Getter) + + self._AssertInstanceSequence(results, 1) + self.assertEquals(1, len(custom_getter_called)) + -class ListPagerAttributeTest(unittest2.TestCase): +class ListPagerAttributeTest(unittest.TestCase): def setUp(self): self.mocked_client = mock.Client(iam_client.IamV1) diff --git a/apitools/base/py/stream_slice_test.py b/apitools/base/py/stream_slice_test.py index 4d5cdfb..f29e112 100644 --- a/apitools/base/py/stream_slice_test.py +++ b/apitools/base/py/stream_slice_test.py @@ -16,15 +16,15 @@ """Tests for stream_slice.""" import string +import unittest import six -import unittest2 from apitools.base.py import exceptions from apitools.base.py import stream_slice -class StreamSliceTest(unittest2.TestCase): +class StreamSliceTest(unittest.TestCase): def setUp(self): self.stream = six.StringIO(string.ascii_letters) diff --git a/apitools/base/py/testing/mock.py b/apitools/base/py/testing/mock.py index 3bd38ba..ae6ad89 100644 --- a/apitools/base/py/testing/mock.py +++ b/apitools/base/py/testing/mock.py @@ -170,7 +170,8 @@ class _ExpectedRequestResponse(object): The response that was specified to be returned. """ - if key != self.__key or not _MessagesEqual(request, self.__request): + if key != self.__key or not (self.__request == request or + _MessagesEqual(request, self.__request)): raise UnexpectedRequestException((key, request), (self.__key, self.__request)) diff --git a/apitools/base/py/testing/mock_test.py b/apitools/base/py/testing/mock_test.py index 4afdf7b..9bd8f05 100644 --- a/apitools/base/py/testing/mock_test.py +++ b/apitools/base/py/testing/mock_test.py @@ -15,8 +15,9 @@ """Tests for apitools.base.py.testing.mock.""" +import unittest + import httplib2 -import unittest2 import six from apitools.base.protorpclite import messages @@ -42,7 +43,7 @@ class CustomException(Exception): pass -class MockTest(unittest2.TestCase): +class MockTest(unittest.TestCase): def testMockFusionBasic(self): with mock.Client(fusiontables.FusiontablesV1) as client_class: @@ -151,6 +152,38 @@ class MockTest(unittest2.TestCase): client = fusiontables.FusiontablesV1(get_credentials=False) self.assertNotEqual(type(client.column), mocked_service_type) + def testRequestMacher(self): + class Matcher(object): + def __init__(self, eq): + self._eq = eq + + def __eq__(self, other): + return self._eq(other) + + with mock.Client(fusiontables.FusiontablesV1) as client_class: + def IsEven(x): + return x % 2 == 0 + + def IsOdd(x): + return not IsEven(x) + + client_class.column.List.Expect( + request=Matcher(IsEven), response=1, + enable_type_checking=False) + client_class.column.List.Expect( + request=Matcher(IsOdd), response=2, enable_type_checking=False) + client_class.column.List.Expect( + request=Matcher(IsEven), response=3, + enable_type_checking=False) + client_class.column.List.Expect( + request=Matcher(IsOdd), response=4, enable_type_checking=False) + + client = fusiontables.FusiontablesV1(get_credentials=False) + self.assertEqual(client.column.List(2), 1) + self.assertEqual(client.column.List(1), 2) + self.assertEqual(client.column.List(20), 3) + self.assertEqual(client.column.List(23), 4) + def testClientUnmock(self): mock_client = mock.Client(fusiontables.FusiontablesV1) self.assertFalse(isinstance(mock_client, fusiontables.FusiontablesV1)) @@ -220,7 +253,7 @@ class _NestedNestedMessage(messages.Message): nested = messages.MessageField(_NestedMessage, 1) -class UtilTest(unittest2.TestCase): +class UtilTest(unittest.TestCase): def testMessagesEqual(self): self.assertFalse(mock._MessagesEqual( diff --git a/apitools/base/py/transfer_test.py b/apitools/base/py/transfer_test.py index c68e77e..4a9e79c 100644 --- a/apitools/base/py/transfer_test.py +++ b/apitools/base/py/transfer_test.py @@ -16,12 +16,12 @@ """Tests for transfer.py.""" import string +import unittest import httplib2 import mock import six from six.moves import http_client -import unittest2 from apitools.base.py import base_api from apitools.base.py import exceptions @@ -30,7 +30,7 @@ from apitools.base.py import http_wrapper from apitools.base.py import transfer -class TransferTest(unittest2.TestCase): +class TransferTest(unittest.TestCase): def assertRangeAndContentRangeCompatible(self, request, response): request_prefix = 'bytes=' @@ -311,7 +311,7 @@ class TransferTest(unittest2.TestCase): self.assertTrue(rewritten_upload_contents.endswith(upload_bytes)) -class UploadTest(unittest2.TestCase): +class UploadTest(unittest.TestCase): def setUp(self): # Sample highly compressible data. diff --git a/apitools/base/py/util.py b/apitools/base/py/util.py index ac1a44c..ad086e4 100644 --- a/apitools/base/py/util.py +++ b/apitools/base/py/util.py @@ -16,7 +16,6 @@ """Assorted utilities shared between parts of apitools.""" -import collections import os import random @@ -30,6 +29,11 @@ from apitools.base.protorpclite import messages from apitools.base.py import encoding_helper as encoding from apitools.base.py import exceptions +if six.PY3: + from collections.abc import Iterable +else: + from collections import Iterable + __all__ = [ 'DetectGae', 'DetectGce', @@ -78,7 +82,7 @@ def NormalizeScopes(scope_spec): if isinstance(scope_spec, six.string_types): scope_spec = six.ensure_str(scope_spec) return set(scope_spec.split(' ')) - elif isinstance(scope_spec, collections.Iterable): + elif isinstance(scope_spec, Iterable): scope_spec = [six.ensure_str(x) for x in scope_spec] return set(scope_spec) raise exceptions.TypecheckError( diff --git a/apitools/base/py/util_test.py b/apitools/base/py/util_test.py index b2ece27..c3a4732 100644 --- a/apitools/base/py/util_test.py +++ b/apitools/base/py/util_test.py @@ -14,7 +14,7 @@ # limitations under the License. """Tests for util.py.""" -import unittest2 +import unittest from apitools.base.protorpclite import messages from apitools.base.py import encoding @@ -48,7 +48,7 @@ encoding.AddCustomJsonEnumMapping( MessageWithRemappings.AnEnum, 'value_one', 'ONE') -class UtilTest(unittest2.TestCase): +class UtilTest(unittest.TestCase): def testExpand(self): method_config_xy = MockedMethodConfig(relative_path='{x}/y/{z}', diff --git a/apitools/gen/client_generation_test.py b/apitools/gen/client_generation_test.py index 9146501..4e382dd 100644 --- a/apitools/gen/client_generation_test.py +++ b/apitools/gen/client_generation_test.py @@ -22,15 +22,11 @@ import six import subprocess import sys import tempfile +import unittest from apitools.gen import gen_client from apitools.gen import test_utils -if six.PY2: - import unittest2 as unittest -else: - import unittest - _API_LIST = [ 'bigquery.v2', 'compute.v1', diff --git a/apitools/gen/gen_client_test.py b/apitools/gen/gen_client_test.py index 6c4e9b1..a0f30d5 100644 --- a/apitools/gen/gen_client_test.py +++ b/apitools/gen/gen_client_test.py @@ -16,8 +16,7 @@ """Test for gen_client module.""" import os - -import unittest2 +import unittest from apitools.gen import gen_client from apitools.gen import test_utils @@ -32,7 +31,7 @@ def _GetContent(file_path): return f.read() -class ClientGenCliTest(unittest2.TestCase): +class ClientGenCliTest(unittest.TestCase): def testHelp_NotEnoughArguments(self): with self.assertRaisesRegexp(SystemExit, '0'): diff --git a/apitools/gen/service_registry.py b/apitools/gen/service_registry.py index e47b050..b79f0d1 100644 --- a/apitools/gen/service_registry.py +++ b/apitools/gen/service_registry.py @@ -218,6 +218,7 @@ class ServiceRegistry(object): printer() printer('MESSAGES_MODULE = messages') printer('BASE_URL = {0!r}'.format(client_info.base_url)) + printer('MTLS_BASE_URL = {0!r}'.format(client_info.mtls_base_url)) printer() printer('_PACKAGE = {0!r}'.format(client_info.package)) printer('_SCOPES = {0!r}'.format( diff --git a/apitools/gen/test_utils.py b/apitools/gen/test_utils.py index 484dcbc..e6b5373 100644 --- a/apitools/gen/test_utils.py +++ b/apitools/gen/test_utils.py @@ -20,12 +20,12 @@ import os import shutil import sys import tempfile +import unittest import six -import unittest2 -SkipOnWindows = unittest2.skipIf( +SkipOnWindows = unittest.skipIf( os.name == 'nt', 'Does not run on windows') diff --git a/apitools/gen/util.py b/apitools/gen/util.py index 680d84a..c2955a8 100644 --- a/apitools/gen/util.py +++ b/apitools/gen/util.py @@ -93,7 +93,7 @@ class Names(object): name = re.sub('[^_A-Za-z0-9]', '_', name) if name[0].isdigit(): name = '_%s' % name - while keyword.iskeyword(name): + while keyword.iskeyword(name) or name == 'exec': name = '%s_' % name # If we end up with __ as a prefix, we'll run afoul of python # field renaming, so we manually correct for it. @@ -174,9 +174,21 @@ def NormalizeVersion(version): return version.replace('.', '_') -def _ComputePaths(package, version, discovery_doc): - full_path = urllib_parse.urljoin( - discovery_doc['rootUrl'], discovery_doc['servicePath']) +def _ComputePaths(package, version, root_url, service_path): + """Compute the base url and base path. + + Attributes: + package: name field of the discovery, i.e. 'storage' for storage service. + version: version of the service, i.e. 'v1'. + root_url: root url of the service, i.e. 'https://www.googleapis.com/'. + service_path: path of the service under the rool url, i.e. 'storage/v1/'. + + Returns: + base url: string, base url of the service, + 'https://www.googleapis.com/storage/v1/' for the storage service. + base path: string, common prefix of service endpoints after the base url. + """ + full_path = urllib_parse.urljoin(root_url, service_path) api_path_component = '/'.join((package, version, '')) if api_path_component not in full_path: return full_path, '' @@ -187,7 +199,7 @@ def _ComputePaths(package, version, discovery_doc): class ClientInfo(collections.namedtuple('ClientInfo', ( 'package', 'scopes', 'version', 'client_id', 'client_secret', 'user_agent', 'client_class_name', 'url_version', 'api_key', - 'base_url', 'base_path'))): + 'base_url', 'base_path', 'mtls_base_url'))): """Container for client-related info and names.""" @@ -201,7 +213,15 @@ class ClientInfo(collections.namedtuple('ClientInfo', ( package = discovery_doc['name'] url_version = discovery_doc['version'] base_url, base_path = _ComputePaths(package, url_version, - discovery_doc) + discovery_doc['rootUrl'], + discovery_doc['servicePath']) + + mtls_root_url = discovery_doc.get('mtlsRootUrl', '') + mtls_base_url = '' + if mtls_root_url: + mtls_base_url, _ = _ComputePaths(package, url_version, + mtls_root_url, + discovery_doc['servicePath']) client_info = { 'package': package, @@ -214,6 +234,7 @@ class ClientInfo(collections.namedtuple('ClientInfo', ( 'api_key': api_key, 'base_url': base_url, 'base_path': base_path, + 'mtls_base_url': mtls_base_url, } client_class_name = '%s%s' % ( names.ClassName(client_info['package']), @@ -403,7 +424,8 @@ def FetchDiscoveryDoc(discovery_url, retries=5): if isinstance(content, bytes): content = content.decode('utf8') discovery_doc = json.loads(content) - break + if discovery_doc: + return discovery_doc except (urllib_error.HTTPError, urllib_error.URLError) as e: logging.info( 'Attempting to fetch discovery doc again after "%s"', e) @@ -412,4 +434,3 @@ def FetchDiscoveryDoc(discovery_url, retries=5): raise CommunicationError( 'Could not find discovery doc at any of %s: %s' % ( discovery_urls, last_exception)) - return discovery_doc diff --git a/apitools/gen/util_test.py b/apitools/gen/util_test.py index 7668b53..9682bf9 100644 --- a/apitools/gen/util_test.py +++ b/apitools/gen/util_test.py @@ -21,13 +21,13 @@ import gzip import os import six.moves.urllib.request as urllib_request import tempfile -import unittest2 +import unittest from apitools.gen import util from mock import patch -class NormalizeVersionTest(unittest2.TestCase): +class NormalizeVersionTest(unittest.TestCase): def testVersions(self): already_valid = 'v1' @@ -36,7 +36,7 @@ class NormalizeVersionTest(unittest2.TestCase): self.assertEqual('v0_1', util.NormalizeVersion(to_clean)) -class NamesTest(unittest2.TestCase): +class NamesTest(unittest.TestCase): def testKeywords(self): names = util.Names(['']) @@ -81,7 +81,7 @@ def _Gzip(raw_content): os.unlink(f.name) -class GetURLContentTest(unittest2.TestCase): +class GetURLContentTest(unittest.TestCase): def testUnspecifiedContentEncoding(self): data = 'regular non-gzipped content' diff --git a/samples/bigquery_sample/bigquery_v2/bigquery_v2_client.py b/samples/bigquery_sample/bigquery_v2/bigquery_v2_client.py index e6cf9c8..90552da 100644 --- a/samples/bigquery_sample/bigquery_v2/bigquery_v2_client.py +++ b/samples/bigquery_sample/bigquery_v2/bigquery_v2_client.py @@ -9,6 +9,7 @@ class BigqueryV2(base_api.BaseApiClient): MESSAGES_MODULE = messages BASE_URL = u'https://www.googleapis.com/bigquery/v2/' + MTLS_BASE_URL = u'' _PACKAGE = u'bigquery' _SCOPES = [u'https://www.googleapis.com/auth/bigquery', u'https://www.googleapis.com/auth/bigquery.insertdata', u'https://www.googleapis.com/auth/cloud-platform', u'https://www.googleapis.com/auth/cloud-platform.read-only', u'https://www.googleapis.com/auth/devstorage.full_control', u'https://www.googleapis.com/auth/devstorage.read_only', u'https://www.googleapis.com/auth/devstorage.read_write'] diff --git a/samples/dns_sample/dns_v1/dns_v1_client.py b/samples/dns_sample/dns_v1/dns_v1_client.py index ce3aff6..0666460 100644 --- a/samples/dns_sample/dns_v1/dns_v1_client.py +++ b/samples/dns_sample/dns_v1/dns_v1_client.py @@ -9,6 +9,7 @@ class DnsV1(base_api.BaseApiClient): MESSAGES_MODULE = messages BASE_URL = u'https://www.googleapis.com/dns/v1/' + MTLS_BASE_URL = u'' _PACKAGE = u'dns' _SCOPES = [u'https://www.googleapis.com/auth/cloud-platform', u'https://www.googleapis.com/auth/cloud-platform.read-only', u'https://www.googleapis.com/auth/ndev.clouddns.readonly', u'https://www.googleapis.com/auth/ndev.clouddns.readwrite'] diff --git a/samples/dns_sample/gen_dns_client_test.py b/samples/dns_sample/gen_dns_client_test.py index dff6812..862ddba 100644 --- a/samples/dns_sample/gen_dns_client_test.py +++ b/samples/dns_sample/gen_dns_client_test.py @@ -15,7 +15,8 @@ """Test for generated sample module.""" -import unittest2 +import unittest + import six from apitools.base.py import list_pager @@ -25,7 +26,7 @@ from samples.dns_sample.dns_v1 import dns_v1_client from samples.dns_sample.dns_v1 import dns_v1_messages -class DnsGenClientSanityTest(unittest2.TestCase): +class DnsGenClientSanityTest(unittest.TestCase): def testBaseUrl(self): self.assertEquals(u'https://www.googleapis.com/dns/v1/', @@ -46,7 +47,7 @@ class DnsGenClientSanityTest(unittest2.TestCase): 'ResourceRecordSetsService']), inner_classes) -class DnsGenClientTest(unittest2.TestCase): +class DnsGenClientTest(unittest.TestCase): def setUp(self): self.mocked_dns_v1 = mock.Client(dns_v1_client.DnsV1) diff --git a/samples/fusiontables_sample/fusiontables_v1/fusiontables_v1_client.py b/samples/fusiontables_sample/fusiontables_v1/fusiontables_v1_client.py index f80fb3e..b7b6c43 100644 --- a/samples/fusiontables_sample/fusiontables_v1/fusiontables_v1_client.py +++ b/samples/fusiontables_sample/fusiontables_v1/fusiontables_v1_client.py @@ -9,6 +9,7 @@ class FusiontablesV1(base_api.BaseApiClient): MESSAGES_MODULE = messages BASE_URL = u'https://www.googleapis.com/fusiontables/v1/' + MTLS_BASE_URL = u'' _PACKAGE = u'fusiontables' _SCOPES = [u'https://www.googleapis.com/auth/fusiontables', u'https://www.googleapis.com/auth/fusiontables.readonly'] diff --git a/samples/iam_sample/iam_client_test.py b/samples/iam_sample/iam_client_test.py index 39d25a4..017a2d0 100644 --- a/samples/iam_sample/iam_client_test.py +++ b/samples/iam_sample/iam_client_test.py @@ -15,7 +15,8 @@ """Test for generated sample module.""" -import unittest2 +import unittest + import six from apitools.base.py.testing import mock @@ -24,7 +25,7 @@ from samples.iam_sample.iam_v1 import iam_v1_client # nopep8 from samples.iam_sample.iam_v1 import iam_v1_messages # nopep8 -class DnsGenClientSanityTest(unittest2.TestCase): +class DnsGenClientSanityTest(unittest.TestCase): def testBaseUrl(self): self.assertEquals(u'https://iam.googleapis.com/', @@ -46,7 +47,7 @@ class DnsGenClientSanityTest(unittest2.TestCase): 'RolesService']), inner_classes) -class IamGenClientTest(unittest2.TestCase): +class IamGenClientTest(unittest.TestCase): def setUp(self): self.mocked_iam_v1 = mock.Client(iam_v1_client.IamV1) diff --git a/samples/iam_sample/iam_v1/iam_v1_client.py b/samples/iam_sample/iam_v1/iam_v1_client.py index 9f333ef..ed9112e 100644 --- a/samples/iam_sample/iam_v1/iam_v1_client.py +++ b/samples/iam_sample/iam_v1/iam_v1_client.py @@ -9,6 +9,7 @@ class IamV1(base_api.BaseApiClient): MESSAGES_MODULE = messages BASE_URL = u'https://iam.googleapis.com/' + MTLS_BASE_URL = u'' _PACKAGE = u'iam' _SCOPES = [u'https://www.googleapis.com/auth/cloud-platform'] diff --git a/samples/servicemanagement_sample/messages_test.py b/samples/servicemanagement_sample/messages_test.py index a62dbd7..5f56322 100644 --- a/samples/servicemanagement_sample/messages_test.py +++ b/samples/servicemanagement_sample/messages_test.py @@ -15,7 +15,7 @@ """Test for generated servicemanagement messages module.""" -import unittest2 +import unittest from apitools.base.py import extra_types @@ -23,7 +23,7 @@ from samples.servicemanagement_sample.servicemanagement_v1 \ import servicemanagement_v1_messages as messages # nopep8 -class MessagesTest(unittest2.TestCase): +class MessagesTest(unittest.TestCase): def testInstantiateMessageWithAdditionalProperties(self): PROJECT_NAME = 'test-project' diff --git a/samples/servicemanagement_sample/servicemanagement_v1/servicemanagement_v1_client.py b/samples/servicemanagement_sample/servicemanagement_v1/servicemanagement_v1_client.py index a72936e..25823db 100644 --- a/samples/servicemanagement_sample/servicemanagement_v1/servicemanagement_v1_client.py +++ b/samples/servicemanagement_sample/servicemanagement_v1/servicemanagement_v1_client.py @@ -9,6 +9,7 @@ class ServicemanagementV1(base_api.BaseApiClient): MESSAGES_MODULE = messages BASE_URL = u'https://servicemanagement.googleapis.com/' + MTLS_BASE_URL = u'' _PACKAGE = u'servicemanagement' _SCOPES = [u'https://www.googleapis.com/auth/cloud-platform', u'https://www.googleapis.com/auth/service.management'] diff --git a/samples/storage_sample/storage_v1.json b/samples/storage_sample/storage_v1.json index 2636bda..cfd2748 100644 --- a/samples/storage_sample/storage_v1.json +++ b/samples/storage_sample/storage_v1.json @@ -21,6 +21,7 @@ "baseUrl": "https://www.googleapis.com/storage/v1/", "basePath": "/storage/v1/", "rootUrl": "https://www.googleapis.com/", + "mtlsRootUrl": "https://www.mtls.googleapis.com/", "servicePath": "storage/v1/", "batchPath": "batch/storage/v1", "parameters": { diff --git a/samples/storage_sample/storage_v1/storage_v1_client.py b/samples/storage_sample/storage_v1/storage_v1_client.py index 38ceab9..4a8414a 100644 --- a/samples/storage_sample/storage_v1/storage_v1_client.py +++ b/samples/storage_sample/storage_v1/storage_v1_client.py @@ -9,6 +9,7 @@ class StorageV1(base_api.BaseApiClient): MESSAGES_MODULE = messages BASE_URL = u'https://www.googleapis.com/storage/v1/' + MTLS_BASE_URL = u'https://www.mtls.googleapis.com/storage/v1/' _PACKAGE = u'storage' _SCOPES = [u'https://www.googleapis.com/auth/cloud-platform', u'https://www.googleapis.com/auth/cloud-platform.read-only', u'https://www.googleapis.com/auth/devstorage.full_control', u'https://www.googleapis.com/auth/devstorage.read_only', u'https://www.googleapis.com/auth/devstorage.read_write'] diff --git a/samples/uptodate_check_test.py b/samples/uptodate_check_test.py index 3871695..8ca258e 100644 --- a/samples/uptodate_check_test.py +++ b/samples/uptodate_check_test.py @@ -14,9 +14,9 @@ import os import difflib +import unittest import six -import unittest2 from apitools.gen import gen_client from apitools.gen import test_utils @@ -31,7 +31,7 @@ def _GetContent(file_path): return f.read() -class ClientGenCliTest(unittest2.TestCase): +class ClientGenCliTest(unittest.TestCase): def AssertDiffEqual(self, expected, actual): """Like unittest.assertEqual with a diff in the exception message.""" @@ -39,7 +39,6 @@ CLI_PACKAGES = [ ] TESTING_PACKAGES = [ - 'unittest2>=0.5.1', 'mock>=1.0.1', ] @@ -49,7 +48,7 @@ CONSOLE_SCRIPTS = [ py_version = platform.python_version() -_APITOOLS_VERSION = '0.5.30' +_APITOOLS_VERSION = '0.5.31' with open('README.rst') as fileobj: README = fileobj.read() @@ -62,6 +61,7 @@ setuptools.setup( url='http://github.com/google/apitools', author='Craig Citro', author_email='craigcitro@google.com', + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', # Contained modules and scripts. packages=setuptools.find_packages(include=['apitools']), entry_points={'console_scripts': CONSOLE_SCRIPTS}, @@ -88,6 +88,10 @@ setuptools.setup( # PyPI package information. classifiers=[ 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries :: Python Modules', ], @@ -1,9 +1,6 @@ [tox] envlist = - py26-oauth2client4 py27-oauth2client{1,2,3,4} - py33-oauth2client41 - py34-oauth2client41 py35-oauth2client{1,2,3,4} [testenv] @@ -28,7 +25,6 @@ commands = deps = pycodestyle==2.4.0 pylint - unittest2 [testenv:cover] basepython = @@ -39,7 +35,6 @@ deps = python-gflags mock nose - unittest2 coverage nosexcover @@ -58,7 +53,6 @@ basepython = deps = mock nose - unittest2 coverage commands = coverage run --branch -p samples/storage_sample/downloads_test.py |