From 9f7b4109b370e89c29db6c58c6bd2e09002c8d42 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Fri, 23 Jul 2021 12:18:25 -0400 Subject: [PATCH] fix: drop six dependency (#1452) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #1446 🦕 --- apiclient/__init__.py | 4 +- googleapiclient/_helpers.py | 9 ++-- googleapiclient/channel.py | 5 +- googleapiclient/discovery.py | 57 ++++++++++---------- googleapiclient/http.py | 54 +++++++++---------- googleapiclient/mimeparse.py | 3 +- googleapiclient/model.py | 18 +++---- googleapiclient/schema.py | 3 +- samples/compute/create_instance.py | 1 - setup.py | 1 - testing/constraints-3.6.txt | 1 - tests/test__helpers.py | 5 +- tests/test_discovery.py | 83 +++++++++++++----------------- tests/test_http.py | 58 ++++++--------------- tests/test_json_model.py | 21 +++----- tests/test_protobuf_model.py | 4 -- 16 files changed, 126 insertions(+), 201 deletions(-) diff --git a/apiclient/__init__.py b/apiclient/__init__.py index 8d9c4ecb8f3..abacd294724 100644 --- a/apiclient/__init__.py +++ b/apiclient/__init__.py @@ -1,7 +1,5 @@ """Retain apiclient as an alias for googleapiclient.""" -from six import iteritems - import googleapiclient from googleapiclient import channel @@ -32,5 +30,5 @@ import sys -for module_name, module in iteritems(_SUBMODULES): +for module_name, module in _SUBMODULES.items(): sys.modules["apiclient.%s" % module_name] = module diff --git a/googleapiclient/_helpers.py b/googleapiclient/_helpers.py index ddbd0e27279..eb5e0904291 100644 --- a/googleapiclient/_helpers.py +++ b/googleapiclient/_helpers.py @@ -17,10 +17,7 @@ import functools import inspect import logging -import warnings - -import six -from six.moves import urllib +import urllib logger = logging.getLogger(__name__) @@ -135,7 +132,7 @@ def positional_wrapper(*args, **kwargs): return positional_wrapper - if isinstance(max_positional_args, six.integer_types): + if isinstance(max_positional_args, int): return positional_decorator else: args, _, _, defaults = inspect.getargspec(max_positional_args) @@ -156,7 +153,7 @@ def parse_unique_urlencoded(content): """ urlencoded_params = urllib.parse.parse_qs(content) params = {} - for key, value in six.iteritems(urlencoded_params): + for key, value in urlencoded_params.items(): if len(value) != 1: msg = "URL-encoded content contains a repeated value:" "%s -> %s" % ( key, diff --git a/googleapiclient/channel.py b/googleapiclient/channel.py index efff0f657f3..70af779e59f 100644 --- a/googleapiclient/channel.py +++ b/googleapiclient/channel.py @@ -76,7 +76,6 @@ from googleapiclient import errors from googleapiclient import _helpers as util -import six # The unix time epoch starts at midnight 1970. @@ -104,7 +103,7 @@ def _upper_header_keys(headers): new_headers = {} - for k, v in six.iteritems(headers): + for k, v in headers.items(): new_headers[k.upper()] = v return new_headers @@ -244,7 +243,7 @@ def update(self, resp): Args: resp: dict, The response from a watch() method. """ - for json_name, param_name in six.iteritems(CHANNEL_PARAMS): + for json_name, param_name in CHANNEL_PARAMS.items(): value = resp.get(json_name) if value is not None: setattr(self, param_name, value) diff --git a/googleapiclient/discovery.py b/googleapiclient/discovery.py index 3273899a029..1b7aedd6fd1 100644 --- a/googleapiclient/discovery.py +++ b/googleapiclient/discovery.py @@ -17,31 +17,26 @@ A client library for Google's discovery based APIs. """ from __future__ import absolute_import -import six __author__ = "jcgregorio@google.com (Joe Gregorio)" __all__ = ["build", "build_from_document", "fix_method_name", "key2param"] -from six.moves import http_client -from six.moves.urllib.parse import urljoin - - # Standard library imports import copy from collections import OrderedDict - -try: - from email.generator import BytesGenerator -except ImportError: - from email.generator import Generator as BytesGenerator +import collections.abc +from email.generator import BytesGenerator from email.mime.multipart import MIMEMultipart from email.mime.nonmultipart import MIMENonMultipart +import http.client as http_client +import io import json import keyword import logging import mimetypes import os import re +import urllib # Third-party imports import httplib2 @@ -506,7 +501,7 @@ def build_from_document( if client_options is None: client_options = google.api_core.client_options.ClientOptions() - if isinstance(client_options, six.moves.collections_abc.Mapping): + if isinstance(client_options, collections.abc.Mapping): client_options = google.api_core.client_options.from_dict(client_options) if http is not None: @@ -519,9 +514,9 @@ def build_from_document( if option is not None: raise ValueError("Arguments http and {} are mutually exclusive".format(name)) - if isinstance(service, six.string_types): + if isinstance(service, str): service = json.loads(service) - elif isinstance(service, six.binary_type): + elif isinstance(service, bytes): service = json.loads(service.decode("utf-8")) if "rootUrl" not in service and isinstance(http, (HttpMock, HttpMockSequence)): @@ -534,7 +529,7 @@ def build_from_document( raise InvalidJsonError() # If an API Endpoint is provided on client options, use that as the base URL - base = urljoin(service["rootUrl"], service["servicePath"]) + base = urllib.parse.urljoin(service["rootUrl"], service["servicePath"]) if client_options.api_endpoint: base = client_options.api_endpoint @@ -630,7 +625,7 @@ def build_from_document( if "mtlsRootUrl" in service and ( not client_options or not client_options.api_endpoint ): - mtls_endpoint = urljoin(service["mtlsRootUrl"], service["servicePath"]) + mtls_endpoint = urllib.parse.urljoin(service["mtlsRootUrl"], service["servicePath"]) use_mtls_endpoint = os.getenv(GOOGLE_API_USE_MTLS_ENDPOINT, "auto") if not use_mtls_endpoint in ("never", "auto", "always"): @@ -759,7 +754,7 @@ def _fix_up_parameters(method_desc, root_desc, http_method, schema): parameters = method_desc.setdefault("parameters", {}) # Add in the parameters common to all methods. - for name, description in six.iteritems(root_desc.get("parameters", {})): + for name, description in root_desc.get("parameters", {}).items(): parameters[name] = description # Add in undocumented query parameters. @@ -875,7 +870,7 @@ def _urljoin(base, url): # exception here is the case of media uploads, where url will be an # absolute url. if url.startswith("http://") or url.startswith("https://"): - return urljoin(base, url) + 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 @@ -943,7 +938,7 @@ def set_parameters(self, method_desc): """ parameters = method_desc.get("parameters", {}) sorted_parameters = OrderedDict(sorted(parameters.items())) - for arg, desc in six.iteritems(sorted_parameters): + for arg, desc in sorted_parameters.items(): param = key2param(arg) self.argmap[param] = arg @@ -997,9 +992,9 @@ def createMethod(methodName, methodDesc, rootDesc, schema): def method(self, **kwargs): # Don't bother with doc string, it will be over-written by createMethod. - for name in six.iterkeys(kwargs): + for name in kwargs: if name not in parameters.argmap: - raise TypeError('Got an unexpected keyword argument "%s"' % name) + raise TypeError('Got an unexpected keyword argument {}'.format(name)) # Remove args that have a value of None. keys = list(kwargs.keys()) @@ -1016,9 +1011,9 @@ def method(self, **kwargs): ): raise TypeError('Missing required parameter "%s"' % name) - for name, regex in six.iteritems(parameters.pattern_params): + for name, regex in parameters.pattern_params.items(): if name in kwargs: - if isinstance(kwargs[name], six.string_types): + if isinstance(kwargs[name], str): pvalues = [kwargs[name]] else: pvalues = kwargs[name] @@ -1029,13 +1024,13 @@ def method(self, **kwargs): % (name, pvalue, regex) ) - for name, enums in six.iteritems(parameters.enum_params): + for name, enums in parameters.enum_params.items(): if name in kwargs: # We need to handle the case of a repeated enum # name differently, since we want to handle both # arg='value' and arg=['value1', 'value2'] if name in parameters.repeated_params and not isinstance( - kwargs[name], six.string_types + kwargs[name], str ): values = kwargs[name] else: @@ -1049,7 +1044,7 @@ def method(self, **kwargs): actual_query_params = {} actual_path_params = {} - for key, value in six.iteritems(kwargs): + for key, value in kwargs.items(): to_type = parameters.param_types.get(key, "string") # For repeated parameters we cast each member of the list. if key in parameters.repeated_params and type(value) == type([]): @@ -1086,7 +1081,7 @@ def method(self, **kwargs): if media_filename: # Ensure we end up with a valid MediaUpload object. - if isinstance(media_filename, six.string_types): + if isinstance(media_filename, str): if media_mime_type is None: logger.warning( "media_mime_type argument not specified: trying to auto-detect for %s", @@ -1144,7 +1139,7 @@ def method(self, **kwargs): msgRoot.attach(msg) # encode the body: note that we can't use `as_string`, because # it plays games with `From ` lines. - fp = six.BytesIO() + fp = io.BytesIO() g = _BytesGenerator(fp, mangle_from_=False) g.flatten(msgRoot, unixfrom=False) body = fp.getvalue() @@ -1218,7 +1213,7 @@ def method(self, **kwargs): enumDesc = paramdesc.get("enumDescriptions", []) if enum and enumDesc: docs.append(" Allowed values\n") - for (name, desc) in six.moves.zip(enum, enumDesc): + for (name, desc) in zip(enum, enumDesc): docs.append(" %s - %s\n" % (name, desc)) if "response" in methodDesc: if methodName.endswith("_media"): @@ -1415,7 +1410,7 @@ def new_batch_http_request(callback=None): # Add basic methods to Resource if "methods" in resourceDesc: - for methodName, methodDesc in six.iteritems(resourceDesc["methods"]): + for methodName, methodDesc in resourceDesc["methods"].items(): fixedMethodName, method = createMethod( methodName, methodDesc, rootDesc, schema ) @@ -1463,7 +1458,7 @@ def methodResource(self): return (methodName, methodResource) - for methodName, methodDesc in six.iteritems(resourceDesc["resources"]): + for methodName, methodDesc in resourceDesc["resources"].items(): fixedMethodName, method = createResourceMethod(methodName, methodDesc) self._set_dynamic_attr( fixedMethodName, method.__get__(self, self.__class__) @@ -1475,7 +1470,7 @@ def _add_next_methods(self, resourceDesc, schema): # type either the method's request (query parameters) or request body. if "methods" not in resourceDesc: return - for methodName, methodDesc in six.iteritems(resourceDesc["methods"]): + for methodName, methodDesc in resourceDesc["methods"].items(): nextPageTokenName = _findPageTokenName( _methodProperties(methodDesc, schema, "response") ) diff --git a/googleapiclient/http.py b/googleapiclient/http.py index 0dd9c328d17..1b661e1b260 100644 --- a/googleapiclient/http.py +++ b/googleapiclient/http.py @@ -19,15 +19,13 @@ actual HTTP request. """ from __future__ import absolute_import -import six __author__ = "jcgregorio@google.com (Joe Gregorio)" -from six import BytesIO, StringIO -from six.moves.urllib.parse import urlparse, urlunparse, quote, unquote - import copy import httplib2 +import http.client as http_client +import io import json import logging import mimetypes @@ -35,6 +33,7 @@ import random import socket import time +import urllib import uuid # TODO(issue 221): Remove this conditional import jibbajabba. @@ -76,11 +75,6 @@ _LEGACY_BATCH_URI = "https://www.googleapis.com/batch" -if six.PY2: - # That's a builtin python3 exception, nonexistent in python2. - # Defined to None to avoid NameError while trying to catch it - ConnectionError = None - def _should_retry_response(resp_status, content): """Determines whether a response should be retried. @@ -104,7 +98,7 @@ def _should_retry_response(resp_status, content): # For 403 errors, we have to check for the `reason` in the response to # determine if we should retry. - if resp_status == six.moves.http_client.FORBIDDEN: + if resp_status == http_client.FORBIDDEN: # If there's no details about the 403 type, don't retry. if not content: return False @@ -175,7 +169,7 @@ def _retry_request( resp = None content = None exception = None - for retry_num in six.moves.range(num_retries + 1): + for retry_num in range(num_retries + 1): if retry_num > 0: # Sleep before retrying. sleep_time = rand() * 2 ** retry_num @@ -634,7 +628,7 @@ def from_json(s): class MediaInMemoryUpload(MediaIoBaseUpload): """MediaUpload for a chunk of bytes. - DEPRECATED: Use MediaIoBaseUpload with either io.TextIOBase or StringIO for + DEPRECATED: Use MediaIoBaseUpload with either io.TextIOBase or io.StringIO for the stream. """ @@ -648,7 +642,7 @@ def __init__( ): """Create a new MediaInMemoryUpload. - DEPRECATED: Use MediaIoBaseUpload with either io.TextIOBase or StringIO for + DEPRECATED: Use MediaIoBaseUpload with either io.TextIOBase or io.StringIO for the stream. Args: @@ -660,7 +654,7 @@ def __init__( resumable: bool, True if this is a resumable upload. False means upload in a single request. """ - fd = BytesIO(body) + fd = io.BytesIO(body) super(MediaInMemoryUpload, self).__init__( fd, mimetype, chunksize=chunksize, resumable=resumable ) @@ -710,7 +704,7 @@ def __init__(self, fd, request, chunksize=DEFAULT_CHUNK_SIZE): self._rand = random.random self._headers = {} - for k, v in six.iteritems(request.headers): + for k, v in request.headers.items(): # allow users to supply custom headers by setting them on the request # but strip out the ones that are set by default on requests generated by # API methods like Drive's files().get(fileId=...) @@ -917,8 +911,8 @@ def execute(self, http=None, num_retries=0): self.method = "POST" self.headers["x-http-method-override"] = "GET" self.headers["content-type"] = "application/x-www-form-urlencoded" - parsed = urlparse(self.uri) - self.uri = urlunparse( + parsed = urllib.parse.urlparse(self.uri) + self.uri = urllib.parse.urlunparse( (parsed.scheme, parsed.netloc, parsed.path, parsed.params, None, None) ) self.body = parsed.query @@ -1077,7 +1071,7 @@ def next_chunk(self, http=None, num_retries=0): size, ) - for retry_num in six.moves.range(num_retries + 1): + for retry_num in range(num_retries + 1): if retry_num > 0: self._sleep(self._rand() * 2 ** retry_num) LOGGER.warning( @@ -1298,7 +1292,7 @@ def _id_to_header(self, id_): # NB: we intentionally leave whitespace between base/id and '+', so RFC2822 # line folding works properly on Python 3; see # https://github.com/googleapis/google-api-python-client/issues/164 - return "<%s + %s>" % (self._base_id, quote(id_)) + return "<%s + %s>" % (self._base_id, urllib.parse.quote(id_)) def _header_to_id(self, header): """Convert a Content-ID header value to an id. @@ -1321,7 +1315,7 @@ def _header_to_id(self, header): raise BatchError("Invalid value for Content-ID: %s" % header) base, id_ = header[1:-1].split(" + ", 1) - return unquote(id_) + return urllib.parse.unquote(id_) def _serialize_request(self, request): """Convert an HttpRequest object into a string. @@ -1333,8 +1327,8 @@ def _serialize_request(self, request): The request as a string in application/http format. """ # Construct status line - parsed = urlparse(request.uri) - request_line = urlunparse( + parsed = urllib.parse.urlparse(request.uri) + request_line = urllib.parse.urlunparse( ("", "", parsed.path, parsed.params, parsed.query, "") ) status_line = request.method + " " + request_line + " HTTP/1.1\n" @@ -1353,7 +1347,7 @@ def _serialize_request(self, request): if "content-type" in headers: del headers["content-type"] - for key, value in six.iteritems(headers): + for key, value in headers.items(): msg[key] = value msg["Host"] = parsed.netloc msg.set_unixfrom(None) @@ -1363,7 +1357,7 @@ def _serialize_request(self, request): msg["content-length"] = str(len(request.body)) # Serialize the mime message. - fp = StringIO() + fp = io.StringIO() # maxheaderlen=0 means don't line wrap headers. g = Generator(fp, maxheaderlen=0) g.flatten(msg, unixfrom=False) @@ -1488,7 +1482,7 @@ def _execute(self, http, order, requests): # encode the body: note that we can't use `as_string`, because # it plays games with `From ` lines. - fp = StringIO() + fp = io.StringIO() g = Generator(fp, mangle_from_=False) g.flatten(message, unixfrom=False) body = fp.getvalue() @@ -1509,8 +1503,7 @@ def _execute(self, http, order, requests): header = "content-type: %s\r\n\r\n" % resp["content-type"] # PY3's FeedParser only accepts unicode. So we should decode content # here, and encode each payload again. - if six.PY3: - content = content.decode("utf-8") + content = content.decode("utf-8") for_parser = header + content parser = FeedParser() @@ -1526,7 +1519,7 @@ def _execute(self, http, order, requests): request_id = self._header_to_id(part["Content-ID"]) response, content = self._deserialize_response(part.get_payload()) # We encode content here to emulate normal http response. - if isinstance(content, six.text_type): + if isinstance(content, str): content = content.encode("utf-8") self._responses[request_id] = (response, content) @@ -1813,7 +1806,8 @@ def request( # Remember the request so after the fact this mock can be examined self.request_sequence.append((uri, method, body, headers)) resp, content = self._iterable.pop(0) - content = six.ensure_binary(content) + if isinstance(content, str): + content = content.encode("utf-8") if content == b"echo_request_headers": content = headers @@ -1826,7 +1820,7 @@ def request( content = body elif content == b"echo_request_uri": content = uri - if isinstance(content, six.text_type): + if isinstance(content, str): content = content.encode("utf-8") return httplib2.Response(resp), content diff --git a/googleapiclient/mimeparse.py b/googleapiclient/mimeparse.py index 6051628f3ae..a1056677c45 100644 --- a/googleapiclient/mimeparse.py +++ b/googleapiclient/mimeparse.py @@ -23,7 +23,6 @@ """ from __future__ import absolute_import from functools import reduce -import six __version__ = "0.1.3" __author__ = "Joe Gregorio" @@ -105,7 +104,7 @@ def fitness_and_quality_parsed(mime_type, parsed_ranges): lambda x, y: x + y, [ 1 - for (key, value) in six.iteritems(target_params) + for (key, value) in target_params.items() if key != "q" and key in params and value == params[key] ], 0, diff --git a/googleapiclient/model.py b/googleapiclient/model.py index f58549c49ea..b853a4f682e 100644 --- a/googleapiclient/model.py +++ b/googleapiclient/model.py @@ -20,7 +20,6 @@ object representation. """ from __future__ import absolute_import -import six __author__ = "jcgregorio@google.com (Joe Gregorio)" @@ -28,8 +27,7 @@ import logging import platform import pkg_resources - -from six.moves.urllib.parse import urlencode +import urllib from googleapiclient.errors import HttpError @@ -112,11 +110,11 @@ def _log_request(self, headers, path_params, query, body): if dump_request_response: LOGGER.info("--request-start--") LOGGER.info("-headers-start-") - for h, v in six.iteritems(headers): + for h, v in headers.items(): LOGGER.info("%s: %s", h, v) LOGGER.info("-headers-end-") LOGGER.info("-path-parameters-start-") - for h, v in six.iteritems(path_params): + for h, v in path_params.items(): LOGGER.info("%s: %s", h, v) LOGGER.info("-path-parameters-end-") LOGGER.info("body: %s", body) @@ -175,22 +173,22 @@ def _build_query(self, params): if self.alt_param is not None: params.update({"alt": self.alt_param}) astuples = [] - for key, value in six.iteritems(params): + for key, value in params.items(): if type(value) == type([]): for x in value: x = x.encode("utf-8") astuples.append((key, x)) else: - if isinstance(value, six.text_type) and callable(value.encode): + if isinstance(value, str) and callable(value.encode): value = value.encode("utf-8") astuples.append((key, value)) - return "?" + urlencode(astuples) + return "?" + urllib.parse.urlencode(astuples) def _log_response(self, resp, content): """Logs debugging information about the response if requested.""" if dump_request_response: LOGGER.info("--response-start--") - for h, v in six.iteritems(resp): + for h, v in resp.items(): LOGGER.info("%s: %s", h, v) if content: LOGGER.info(content) @@ -385,7 +383,7 @@ def makepatch(original, modified): body=makepatch(original, item)).execute() """ patch = {} - for key, original_value in six.iteritems(original): + for key, original_value in original.items(): modified_value = modified.get(key, None) if modified_value is None: # Use None to signal that the element is deleted diff --git a/googleapiclient/schema.py b/googleapiclient/schema.py index 00f858875b2..95767ef55c8 100644 --- a/googleapiclient/schema.py +++ b/googleapiclient/schema.py @@ -57,7 +57,6 @@ The constructor takes a discovery document in which to look up named schema. """ from __future__ import absolute_import -import six # TODO(jcgregorio) support format, enum, minimum, maximum @@ -255,7 +254,7 @@ def _to_str_impl(self, schema): if "properties" in schema: properties = schema.get("properties", {}) sorted_properties = OrderedDict(sorted(properties.items())) - for pname, pschema in six.iteritems(sorted_properties): + for pname, pschema in sorted_properties.items(): self.emitBegin('"%s": ' % pname) self._to_str_impl(pschema) elif "additionalProperties" in schema: diff --git a/samples/compute/create_instance.py b/samples/compute/create_instance.py index 3ccffc586bc..f9807699ed6 100644 --- a/samples/compute/create_instance.py +++ b/samples/compute/create_instance.py @@ -27,7 +27,6 @@ import time import googleapiclient.discovery -from six.moves import input # [START compute_apiary_list_instances] diff --git a/setup.py b/setup.py index 60bddf12231..76d65edac86 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,6 @@ # Until this issue is closed # https://github.com/googleapis/google-cloud-python/issues/10566 "google-api-core>=1.21.0,<3.0.0dev", - "six>=1.13.0,<2dev", "uritemplate>=3.0.0,<4dev", ] diff --git a/testing/constraints-3.6.txt b/testing/constraints-3.6.txt index ccff147a7db..0c0e7a2e53b 100644 --- a/testing/constraints-3.6.txt +++ b/testing/constraints-3.6.txt @@ -9,5 +9,4 @@ httplib2==0.15.0 google-auth==1.16.0 google-auth-httplib2==0.0.3 google-api-core==1.21.0 -six==1.13.0 uritemplate==3.0.0 \ No newline at end of file diff --git a/tests/test__helpers.py b/tests/test__helpers.py index 90c75efed89..ab0bd4bdb38 100644 --- a/tests/test__helpers.py +++ b/tests/test__helpers.py @@ -15,12 +15,9 @@ """Unit tests for googleapiclient._helpers.""" import unittest +import urllib import mock - -import six -from six.moves import urllib - from googleapiclient import _helpers diff --git a/tests/test_discovery.py b/tests/test_discovery.py index 1202b2db739..9500fbf9f35 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -21,16 +21,14 @@ Unit tests for objects created from discovery documents. """ from __future__ import absolute_import -import six __author__ = "jcgregorio@google.com (Joe Gregorio)" -from six import BytesIO, StringIO -from six.moves.urllib.parse import urlparse, parse_qs - +from collections import defaultdict import copy import datetime import httplib2 +import io import itertools import json import os @@ -38,7 +36,7 @@ import re import sys import unittest2 as unittest -from collections import defaultdict +import urllib from parameterized import parameterized import mock @@ -98,15 +96,15 @@ def assertUrisEqual(testcase, expected, actual): """Test that URIs are the same, up to reordering of query parameters.""" - expected = urlparse(expected) - actual = urlparse(actual) + expected = urllib.parse.urlparse(expected) + actual = urllib.parse.urlparse(actual) testcase.assertEqual(expected.scheme, actual.scheme) testcase.assertEqual(expected.netloc, actual.netloc) testcase.assertEqual(expected.path, actual.path) testcase.assertEqual(expected.params, actual.params) testcase.assertEqual(expected.fragment, actual.fragment) - expected_query = parse_qs(expected.query) - actual_query = parse_qs(actual.query) + expected_query = urllib.parse.parse_qs(expected.query) + actual_query = urllib.parse.parse_qs(actual.query) for name in list(expected_query.keys()): testcase.assertEqual(expected_query[name], actual_query[name]) for name in list(actual_query.keys()): @@ -175,7 +173,7 @@ def _base_fix_up_parameters_test(self, method_desc, http_method, root_desc, sche STACK_QUERY_PARAMETER_DEFAULT_VALUE, parameters[param_name] ) - for param_name, value in six.iteritems(root_desc.get("parameters", {})): + for param_name, value in root_desc.get("parameters", {}).items(): self.assertEqual(value, parameters[param_name]) return parameters @@ -1271,8 +1269,8 @@ def test_method_error_checking(self): self.assertTrue("unexpected" in str(e)) def _check_query_types(self, request): - parsed = urlparse(request.uri) - q = parse_qs(parsed[4]) + parsed = urllib.parse.urlparse(request.uri) + q = urllib.parse.parse_qs(parsed.query) self.assertEqual(q["q"], ["foo"]) self.assertEqual(q["i"], ["1"]) self.assertEqual(q["n"], ["1.0"]) @@ -1319,8 +1317,8 @@ def test_optional_stack_query_parameters(self): zoo = build("zoo", "v1", http=http, static_discovery=False) request = zoo.query(trace="html", fields="description") - parsed = urlparse(request.uri) - q = parse_qs(parsed[4]) + parsed = urllib.parse.urlparse(request.uri) + q = urllib.parse.parse_qs(parsed.query) self.assertEqual(q["trace"], ["html"]) self.assertEqual(q["fields"], ["description"]) @@ -1329,8 +1327,8 @@ def test_string_params_value_of_none_get_dropped(self): zoo = build("zoo", "v1", http=http, static_discovery=False) request = zoo.query(trace=None, fields="description") - parsed = urlparse(request.uri) - q = parse_qs(parsed[4]) + parsed = urllib.parse.urlparse(request.uri) + q = urllib.parse.parse_qs(parsed.query) self.assertFalse("trace" in q) def test_model_added_query_parameters(self): @@ -1338,8 +1336,8 @@ def test_model_added_query_parameters(self): zoo = build("zoo", "v1", http=http, static_discovery=False) request = zoo.animals().get(name="Lion") - parsed = urlparse(request.uri) - q = parse_qs(parsed[4]) + parsed = urllib.parse.urlparse(request.uri) + q = urllib.parse.parse_qs(parsed.query) self.assertEqual(q["alt"], ["json"]) self.assertEqual(request.headers["accept"], "application/json") @@ -1348,8 +1346,8 @@ def test_fallback_to_raw_model(self): zoo = build("zoo", "v1", http=http, static_discovery=False) request = zoo.animals().getmedia(name="Lion") - parsed = urlparse(request.uri) - q = parse_qs(parsed[4]) + parsed = urllib.parse.urlparse(request.uri) + q = urllib.parse.parse_qs(parsed.query) self.assertTrue("alt" not in q) self.assertEqual(request.headers["accept"], "*/*") @@ -1426,8 +1424,8 @@ def test_full_featured(self): self.assertTrue(getattr(zoo, "animals")) request = zoo.animals().list(name="bat", projection="full") - parsed = urlparse(request.uri) - q = parse_qs(parsed[4]) + parsed = urllib.parse.urlparse(request.uri) + q = urllib.parse.parse_qs(parsed.query) self.assertEqual(q["name"], ["bat"]) self.assertEqual(q["projection"], ["full"]) @@ -1436,26 +1434,17 @@ def test_nested_resources(self): zoo = build("zoo", "v1", http=self.http, static_discovery=False) self.assertTrue(getattr(zoo, "animals")) request = zoo.my().favorites().list(max_results="5") - parsed = urlparse(request.uri) - q = parse_qs(parsed[4]) + parsed = urllib.parse.urlparse(request.uri) + q = urllib.parse.parse_qs(parsed.query) self.assertEqual(q["max-results"], ["5"]) - @unittest.skipIf(six.PY3, "print is not a reserved name in Python 3") - def test_methods_with_reserved_names(self): - self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) - zoo = build("zoo", "v1", http=self.http) - self.assertTrue(getattr(zoo, "animals")) - request = zoo.global_().print_().assert_(max_results="5") - parsed = urlparse(request.uri) - self.assertEqual(parsed[2], "/zoo/v1/global/print/assert") - def test_top_level_functions(self): self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) zoo = build("zoo", "v1", http=self.http, static_discovery=False) self.assertTrue(getattr(zoo, "query")) request = zoo.query(q="foo") - parsed = urlparse(request.uri) - q = parse_qs(parsed[4]) + parsed = urllib.parse.urlparse(request.uri) + q = urllib.parse.parse_qs(parsed.query) self.assertEqual(q["q"], ["foo"]) def test_simple_media_uploads(self): @@ -1808,7 +1797,7 @@ def test_media_io_base_stream_unlimited_chunksize_resume(self): zoo = build("zoo", "v1", http=self.http, static_discovery=False) # Set up a seekable stream and try to upload in single chunk. - fd = BytesIO(b'01234"56789"') + fd = io.BytesIO(b'01234"56789"') media_upload = MediaIoBaseUpload( fd=fd, mimetype="text/plain", chunksize=-1, resumable=True ) @@ -1839,7 +1828,7 @@ def test_media_io_base_stream_chunksize_resume(self): zoo = build("zoo", "v1", http=self.http, static_discovery=False) # Set up a seekable stream and try to upload in chunks. - fd = BytesIO(b"0123456789") + fd = io.BytesIO(b"0123456789") media_upload = MediaIoBaseUpload( fd=fd, mimetype="text/plain", chunksize=5, resumable=True ) @@ -1950,7 +1939,7 @@ def test_resumable_media_handle_uploads_of_unknown_size_eof(self): self.http = HttpMock(datafile("zoo.json"), {"status": "200"}) zoo = build("zoo", "v1", http=self.http, static_discovery=False) - fd = BytesIO(b"data goes here") + fd = io.BytesIO(b"data goes here") # Create an upload that doesn't know the full size of the media. upload = MediaIoBaseUpload( @@ -1975,7 +1964,7 @@ def test_resumable_media_handle_resume_of_upload_of_unknown_size(self): zoo = build("zoo", "v1", http=self.http, static_discovery=False) # Create an upload that doesn't know the full size of the media. - fd = BytesIO(b"data goes here") + fd = io.BytesIO(b"data goes here") upload = MediaIoBaseUpload( fd=fd, mimetype="image/png", chunksize=500, resumable=True @@ -2152,8 +2141,8 @@ def test_next_successful_with_next_page_token(self): tasks = build("tasks", "v1", http=self.http) request = tasks.tasklists().list() next_request = tasks.tasklists().list_next(request, {"nextPageToken": "123abc"}) - parsed = list(urlparse(next_request.uri)) - q = parse_qs(parsed[4]) + parsed = urllib.parse.urlparse(next_request.uri) + q = urllib.parse.parse_qs(parsed.query) self.assertEqual(q["pageToken"][0], "123abc") def test_next_successful_with_next_page_token_alternate_name(self): @@ -2161,8 +2150,8 @@ def test_next_successful_with_next_page_token_alternate_name(self): bigquery = build("bigquery", "v2", http=self.http) request = bigquery.tabledata().list(datasetId="", projectId="", tableId="") next_request = bigquery.tabledata().list_next(request, {"pageToken": "123abc"}) - parsed = list(urlparse(next_request.uri)) - q = parse_qs(parsed[4]) + parsed = urllib.parse.urlparse(next_request.uri) + q = urllib.parse.parse_qs(parsed.query) self.assertEqual(q["pageToken"][0], "123abc") def test_next_successful_with_next_page_token_in_body(self): @@ -2192,8 +2181,8 @@ def test_next_successful_with_next_page_token_required(self): drive = build("drive", "v3", http=self.http) request = drive.changes().list(pageToken="startPageToken") next_request = drive.changes().list_next(request, {"nextPageToken": "123abc"}) - parsed = list(urlparse(next_request.uri)) - q = parse_qs(parsed[4]) + parsed = urllib.parse.urlparse(next_request.uri) + q = urllib.parse.parse_qs(parsed.query) self.assertEqual(q["pageToken"][0], "123abc") @@ -2203,8 +2192,8 @@ def test_get_media(self): zoo = build("zoo", "v1", http=http, static_discovery=False) request = zoo.animals().get_media(name="Lion") - parsed = urlparse(request.uri) - q = parse_qs(parsed[4]) + parsed = urllib.parse.urlparse(request.uri) + q = urllib.parse.parse_qs(parsed.query) self.assertEqual(q["alt"], ["media"]) self.assertEqual(request.headers["accept"], "*/*") diff --git a/tests/test_http.py b/tests/test_http.py index bfd9ba865d7..5484269532d 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -19,14 +19,10 @@ Unit tests for the googleapiclient.http. """ from __future__ import absolute_import -from six.moves import range __author__ = "jcgregorio@google.com (Joe Gregorio)" -from six import PY3 -from six import BytesIO, StringIO from io import FileIO -from six.moves.urllib.parse import urlencode # Do not remove the httplib2 import import json @@ -36,6 +32,7 @@ import mock import os import unittest2 as unittest +import urllib import random import socket import ssl @@ -132,7 +129,7 @@ def __init__(self, num_errors, success_json, success_data): def request(self, *args, **kwargs): if not self.num_errors: return httplib2.Response(self.success_json), self.success_data - elif self.num_errors == 5 and PY3: + elif self.num_errors == 5: ex = ConnectionResetError # noqa: F821 elif self.num_errors == 4: ex = httplib2.ServerNotFoundError() @@ -149,11 +146,7 @@ def request(self, *args, **kwargs): ex.errno = socket.errno.WSAETIMEDOUT except AttributeError: # For Linux/Mac: - if PY3: - ex = socket.timeout() - else: - ex = OSError() - ex.errno = socket.errno.ETIMEDOUT + ex = socket.timeout() self.num_errors -= 1 raise ex @@ -214,12 +207,8 @@ class TestMediaUpload(unittest.TestCase): def test_media_file_upload_closes_fd_in___del__(self): file_desc = mock.Mock(spec=io.TextIOWrapper) opener = mock.mock_open(file_desc) - if PY3: - with mock.patch("builtins.open", return_value=opener): - upload = MediaFileUpload(datafile("test_close"), mimetype="text/plain") - else: - with mock.patch("__builtin__.open", return_value=opener): - upload = MediaFileUpload(datafile("test_close"), mimetype="text/plain") + with mock.patch("builtins.open", return_value=opener): + upload = MediaFileUpload(datafile("test_close"), mimetype="text/plain") self.assertIs(upload.stream(), file_desc) del upload file_desc.close.assert_called_once_with() @@ -338,25 +327,10 @@ def test_media_io_base_upload_serializable(self): except NotImplementedError: pass - @unittest.skipIf(PY3, "Strings and Bytes are different types") - def test_media_io_base_upload_from_string_io(self): - f = open(datafile("small.png"), "rb") - fd = StringIO(f.read()) - f.close() - - upload = MediaIoBaseUpload( - fd=fd, mimetype="image/png", chunksize=500, resumable=True - ) - self.assertEqual("image/png", upload.mimetype()) - self.assertEqual(190, upload.size()) - self.assertEqual(True, upload.resumable()) - self.assertEqual(500, upload.chunksize()) - self.assertEqual(b"PNG", upload.getbytes(1, 3)) - f.close() def test_media_io_base_upload_from_bytes(self): f = open(datafile("small.png"), "rb") - fd = BytesIO(f.read()) + fd = io.BytesIO(f.read()) upload = MediaIoBaseUpload( fd=fd, mimetype="image/png", chunksize=500, resumable=True ) @@ -368,7 +342,7 @@ def test_media_io_base_upload_from_bytes(self): def test_media_io_base_upload_raises_on_invalid_chunksize(self): f = open(datafile("small.png"), "rb") - fd = BytesIO(f.read()) + fd = io.BytesIO(f.read()) self.assertRaises( InvalidChunkSizeError, MediaIoBaseUpload, @@ -379,7 +353,7 @@ def test_media_io_base_upload_raises_on_invalid_chunksize(self): ) def test_media_io_base_upload_streamable(self): - fd = BytesIO(b"stuff") + fd = io.BytesIO(b"stuff") upload = MediaIoBaseUpload( fd=fd, mimetype="image/png", chunksize=500, resumable=True ) @@ -388,7 +362,7 @@ def test_media_io_base_upload_streamable(self): def test_media_io_base_next_chunk_retries(self): f = open(datafile("small.png"), "rb") - fd = BytesIO(f.read()) + fd = io.BytesIO(f.read()) upload = MediaIoBaseUpload( fd=fd, mimetype="image/png", chunksize=500, resumable=True ) @@ -423,7 +397,7 @@ def test_media_io_base_next_chunk_retries(self): self.assertEqual([20, 40, 80, 20, 40, 80], sleeptimes) def test_media_io_base_next_chunk_no_retry_403_not_configured(self): - fd = BytesIO(b"i am png") + fd = io.BytesIO(b"i am png") upload = MediaIoBaseUpload( fd=fd, mimetype="image/png", chunksize=500, resumable=True ) @@ -448,7 +422,7 @@ def test_media_io_base_next_chunk_no_retry_403_not_configured(self): def test_media_io_base_empty_file(self): - fd = BytesIO() + fd = io.BytesIO() upload = MediaIoBaseUpload( fd=fd, mimetype="image/png", chunksize=500, resumable=True ) @@ -479,7 +453,7 @@ def setUp(self): http = HttpMock(datafile("zoo.json"), {"status": "200"}) zoo = build("zoo", "v1", http=http, static_discovery=False) self.request = zoo.animals().get_media(name="Lion") - self.fd = BytesIO() + self.fd = io.BytesIO() def test_media_io_base_download(self): self.request.http = HttpMockSequence( @@ -544,7 +518,7 @@ def test_media_io_base_download_custom_request_headers(self): self.assertEqual(result.get("Cache-Control"), "no-store") - download._fd = self.fd = BytesIO() + download._fd = self.fd = io.BytesIO() status, done = download.next_chunk() result = json.loads(self.fd.getvalue().decode("utf-8")) @@ -974,7 +948,7 @@ def test_retry_connection_errors_non_resumable(self): def test_retry_connection_errors_resumable(self): with open(datafile("small.png"), "rb") as small_png_file: - small_png_fd = BytesIO(small_png_file.read()) + small_png_fd = io.BytesIO(small_png_file.read()) upload = MediaIoBaseUpload( fd=small_png_fd, mimetype="image/png", chunksize=500, resumable=True ) @@ -1609,7 +1583,7 @@ def _postproc(resp, content): req = HttpRequest( http, _postproc, - "http://example.com?" + urlencode(query), + "http://example.com?" + urllib.parse.urlencode(query), method="GET", body=None, headers={}, @@ -1632,7 +1606,7 @@ class TestStreamSlice(unittest.TestCase): """Test _StreamSlice.""" def setUp(self): - self.stream = BytesIO(b"0123456789") + self.stream = io.BytesIO(b"0123456789") def test_read(self): s = _StreamSlice(self.stream, 0, 4) diff --git a/tests/test_json_model.py b/tests/test_json_model.py index 68578039e63..416b7be93eb 100644 --- a/tests/test_json_model.py +++ b/tests/test_json_model.py @@ -19,24 +19,22 @@ Unit tests for the JSON model. """ from __future__ import absolute_import -import six __author__ = "jcgregorio@google.com (Joe Gregorio)" -import copy +import httplib2 import json -import os import pkg_resources import platform import unittest2 as unittest -import httplib2 +import urllib + import googleapiclient.model + from googleapiclient.errors import HttpError from googleapiclient.model import JsonModel -from six.moves.urllib.parse import parse_qs - _LIBRARY_VERSION = pkg_resources.get_distribution("google-api-python-client").version @@ -130,14 +128,9 @@ def test_json_build_query(self): self.assertEqual(headers["accept"], "application/json") self.assertEqual(headers["content-type"], "application/json") - query_dict = parse_qs(query[1:]) + query_dict = urllib.parse.parse_qs(query[1:]) self.assertEqual(query_dict["foo"], ["1"]) - if six.PY3: - # Python 3, no need to encode - self.assertEqual(query_dict["bar"], [u"\N{COMET}"]) - else: - # Python 2, encode string - self.assertEqual(query_dict["bar"], [u"\N{COMET}".encode("utf-8")]) + self.assertEqual(query_dict["bar"], [u"\N{COMET}"]) self.assertEqual(query_dict["baz"], ["fe", "fi", "fo", "fum"]) self.assertTrue("qux" not in query_dict) self.assertEqual(body, "{}") @@ -250,7 +243,7 @@ class MockResponse(dict): def __init__(self, items): super(MockResponse, self).__init__() self.status = items["status"] - for key, value in six.iteritems(items): + for key, value in items.items(): self[key] = value old_logging = googleapiclient.model.LOGGER diff --git a/tests/test_protobuf_model.py b/tests/test_protobuf_model.py index 78caf4e572f..4479b13d9d6 100644 --- a/tests/test_protobuf_model.py +++ b/tests/test_protobuf_model.py @@ -24,13 +24,9 @@ import unittest2 as unittest import httplib2 -import googleapiclient.model -from googleapiclient.errors import HttpError from googleapiclient.model import ProtocolBufferModel -from six.moves.urllib.parse import parse_qs - class MockProtocolBuffer(object): def __init__(self, data=None):