fix: drop six dependency (#1452)
Fixes #1446 🦕
diff --git a/googleapiclient/_helpers.py b/googleapiclient/_helpers.py
index ddbd0e2..eb5e090 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 @@
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 @@
"""
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 efff0f6..70af779 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 @@
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 3273899..1b7aedd 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
# 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 @@
"""
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 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 @@
):
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 @@
% (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 @@
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 @@
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 @@
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 @@
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 @@
# 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 @@
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 @@
# 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 0dd9c32..1b661e1 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 @@
# 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 @@
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 @@
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 @@
):
"""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 @@
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 @@
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 @@
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 @@
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 @@
# 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 @@
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 @@
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 @@
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 @@
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 @@
# 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 @@
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 @@
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 @@
# 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 @@
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 6051628..a105667 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 @@
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 f58549c..b853a4f 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 @@
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 @@
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 @@
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 00f8588..95767ef 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 @@
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: