diff --git a/.kokoro/presubmit/system-2.7.cfg b/.kokoro/presubmit/system-2.7.cfg deleted file mode 100644 index 3b6523a19..000000000 --- a/.kokoro/presubmit/system-2.7.cfg +++ /dev/null @@ -1,7 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Only run this nox session. -env_vars: { - key: "NOX_SESSION" - value: "system-2.7" -} \ No newline at end of file diff --git a/README.rst b/README.rst index 0796bb05d..5419ae509 100644 --- a/README.rst +++ b/README.rst @@ -65,6 +65,9 @@ Unsupported Python Versions Python == 3.5: the last released version which supported Python 3.5 was ``google-cloud-storage 1.32.0``, released 2020-10-16. +Python == 2.7: the last released version which supported Python 2.7 was +``google-cloud-storage 1.44.0``, released 2022-01-05. + Mac/Linux ^^^^^^^^^ diff --git a/google/cloud/storage/_helpers.py b/google/cloud/storage/_helpers.py index 68aee0a0c..c8359dc1b 100644 --- a/google/cloud/storage/_helpers.py +++ b/google/cloud/storage/_helpers.py @@ -19,11 +19,9 @@ import base64 from hashlib import md5 -from datetime import datetime import os +from urllib.parse import urlsplit -from six import string_types -from six.moves.urllib.parse import urlsplit from google import resumable_media from google.cloud.storage.constants import _DEFAULT_TIMEOUT from google.cloud.storage.retry import DEFAULT_RETRY @@ -453,20 +451,6 @@ def _base64_md5hash(buffer_object): return base64.b64encode(digest_bytes) -def _convert_to_timestamp(value): - """Convert non-none datetime to timestamp. - - :type value: :class:`datetime.datetime` - :param value: The datetime to convert. - - :rtype: int - :returns: The timestamp. - """ - utc_naive = value.replace(tzinfo=None) - value.utcoffset() - mtime = (utc_naive - datetime(1970, 1, 1)).total_seconds() - return mtime - - def _add_etag_match_headers(headers, **match_parameters): """Add generation match parameters into the given parameters list. @@ -480,7 +464,7 @@ def _add_etag_match_headers(headers, **match_parameters): value = match_parameters.get(snakecase_name) if value is not None: - if isinstance(value, string_types): + if isinstance(value, str): value = [value] headers[header_name] = ", ".join(value) diff --git a/google/cloud/storage/_signing.py b/google/cloud/storage/_signing.py index 403df4e97..a2b7209bc 100644 --- a/google/cloud/storage/_signing.py +++ b/google/cloud/storage/_signing.py @@ -20,7 +20,8 @@ import hashlib import json -import six +import http +import urllib import google.auth.credentials @@ -110,7 +111,7 @@ def get_expiration_seconds_v2(expiration): micros = _helpers._microseconds_from_datetime(expiration) expiration = micros // 10 ** 6 - if not isinstance(expiration, six.integer_types): + if not isinstance(expiration, int): raise TypeError( "Expected an integer timestamp, datetime, or " "timedelta. Got %s" % type(expiration) @@ -118,7 +119,7 @@ def get_expiration_seconds_v2(expiration): return expiration -_EXPIRATION_TYPES = six.integer_types + (datetime.datetime, datetime.timedelta) +_EXPIRATION_TYPES = (int, datetime.datetime, datetime.timedelta) def get_expiration_seconds_v4(expiration): @@ -142,7 +143,7 @@ def get_expiration_seconds_v4(expiration): now = NOW().replace(tzinfo=_helpers.UTC) - if isinstance(expiration, six.integer_types): + if isinstance(expiration, int): seconds = expiration if isinstance(expiration, datetime.datetime): @@ -250,7 +251,7 @@ def canonicalize_v2(method, resource, query_parameters, headers): (key.lower(), value and value.strip() or "") for key, value in query_parameters.items() ) - encoded_qp = six.moves.urllib.parse.urlencode(normalized_qp) + encoded_qp = urllib.parse.urlencode(normalized_qp) canonical_resource = "{}?{}".format(resource, encoded_qp) return _Canonical(method, canonical_resource, normalized_qp, headers) @@ -410,7 +411,7 @@ def generate_signed_url_v2( return "{endpoint}{resource}?{querystring}".format( endpoint=api_access_endpoint, resource=resource, - querystring=six.moves.urllib.parse.urlencode(sorted_signed_query_params), + querystring=urllib.parse.urlencode(sorted_signed_query_params), ) @@ -563,7 +564,7 @@ def generate_signed_url_v4( header_names = [key.lower() for key in headers] if "host" not in header_names: - headers["Host"] = six.moves.urllib.parse.urlparse(api_access_endpoint).netloc + headers["Host"] = urllib.parse.urlparse(api_access_endpoint).netloc if method.upper() == "RESUMABLE": method = "POST" @@ -686,7 +687,7 @@ def _sign_message(message, access_token, service_account_email): request = requests.Request() response = request(url=url, method=method, body=body, headers=headers) - if response.status != six.moves.http_client.OK: + if response.status != http.client.OK: raise exceptions.TransportError( "Error calling the IAM signBytes API: {}".format(response.data) ) @@ -723,4 +724,4 @@ def _quote_param(param): """ if not isinstance(param, bytes): param = str(param) - return six.moves.urllib.parse.quote(param, safe="~") + return urllib.parse.quote(param, safe="~") diff --git a/google/cloud/storage/batch.py b/google/cloud/storage/batch.py index a60df80f8..cbc93397f 100644 --- a/google/cloud/storage/batch.py +++ b/google/cloud/storage/batch.py @@ -24,7 +24,6 @@ import json import requests -import six from google.cloud import _helpers from google.cloud import exceptions @@ -65,13 +64,7 @@ def __init__(self, method, uri, headers, body): lines.append("") lines.append(body) payload = "\r\n".join(lines) - if six.PY2: - # email.message.Message is an old-style class, so we - # cannot use 'super()'. - MIMEApplication.__init__(self, payload, "http", encode_noop) - else: # pragma: NO COVER Python3 - super_init = super(MIMEApplicationHTTP, self).__init__ - super_init(payload, "http", encode_noop) + super().__init__(payload, "http", encode_noop) class _FutureDict(object): @@ -219,11 +212,7 @@ def _prepare_batch_request(self): multi.attach(subrequest) timeout = _timeout - # The `email` package expects to deal with "native" strings - if six.PY2: # pragma: NO COVER Python3 - buf = io.BytesIO() - else: - buf = io.StringIO() + buf = io.StringIO() generator = Generator(buf, False, 0) generator.flatten(multi) payload = buf.getvalue() @@ -315,10 +304,7 @@ def _generate_faux_mime_message(parser, response): [b"Content-Type: ", content_type, b"\nMIME-Version: 1.0\n\n", response.content] ) - if six.PY2: - return parser.parsestr(faux_message) - else: # pragma: NO COVER Python3 - return parser.parsestr(faux_message.decode("utf-8")) + return parser.parsestr(faux_message.decode("utf-8")) def _unpack_batch_response(response): diff --git a/google/cloud/storage/blob.py b/google/cloud/storage/blob.py index 75b22861e..36a090af2 100644 --- a/google/cloud/storage/blob.py +++ b/google/cloud/storage/blob.py @@ -35,15 +35,13 @@ import mimetypes import os import re +from urllib.parse import parse_qsl +from urllib.parse import quote +from urllib.parse import urlencode +from urllib.parse import urlsplit +from urllib.parse import urlunsplit import warnings -import six -from six.moves.urllib.parse import parse_qsl -from six.moves.urllib.parse import quote -from six.moves.urllib.parse import urlencode -from six.moves.urllib.parse import urlsplit -from six.moves.urllib.parse import urlunsplit - from google import resumable_media from google.resumable_media.requests import ChunkedDownload from google.resumable_media.requests import Download @@ -64,7 +62,6 @@ from google.cloud.storage._helpers import _PropertyMixin from google.cloud.storage._helpers import _scalar_property from google.cloud.storage._helpers import _bucket_bound_hostname_url -from google.cloud.storage._helpers import _convert_to_timestamp from google.cloud.storage._helpers import _raise_if_more_than_one_set from google.cloud.storage._helpers import _api_core_retry_to_resumable_media_retry from google.cloud.storage._signing import generate_signed_url_v2 @@ -1303,10 +1300,7 @@ def download_to_filename( updated = self.updated if updated is not None: - if six.PY2: - mtime = _convert_to_timestamp(updated) - else: - mtime = updated.timestamp() + mtime = updated.timestamp() os.utime(file_obj.name, (mtime, mtime)) def download_as_bytes( diff --git a/google/cloud/storage/bucket.py b/google/cloud/storage/bucket.py index 77e87515b..6f738976b 100644 --- a/google/cloud/storage/bucket.py +++ b/google/cloud/storage/bucket.py @@ -18,11 +18,9 @@ import copy import datetime import json +from urllib.parse import urlsplit import warnings -import six -from six.moves.urllib.parse import urlsplit - from google.api_core import datetime_helpers from google.cloud._helpers import _datetime_to_rfc3339 from google.cloud._helpers import _NOW @@ -1705,7 +1703,7 @@ def delete_blobs( for blob in blobs: try: blob_name = blob - if not isinstance(blob_name, six.string_types): + if not isinstance(blob_name, str): blob_name = blob.name self.delete_blob( blob_name, diff --git a/google/cloud/storage/retry.py b/google/cloud/storage/retry.py index f1ae30f4b..6037cbe1d 100644 --- a/google/cloud/storage/retry.py +++ b/google/cloud/storage/retry.py @@ -20,19 +20,13 @@ from google.auth import exceptions as auth_exceptions -# ConnectionError is a built-in exception only in Python3 and not in Python2. -try: - _RETRYABLE_STDLIB_TYPES = (ConnectionError,) -except NameError: - _RETRYABLE_STDLIB_TYPES = () - - -_RETRYABLE_TYPES = _RETRYABLE_STDLIB_TYPES + ( +_RETRYABLE_TYPES = ( api_exceptions.TooManyRequests, # 429 api_exceptions.InternalServerError, # 500 api_exceptions.BadGateway, # 502 api_exceptions.ServiceUnavailable, # 503 api_exceptions.GatewayTimeout, # 504 + ConnectionError, requests.ConnectionError, requests_exceptions.ChunkedEncodingError, ) diff --git a/noxfile.py b/noxfile.py index 3bb910dc2..318bc3957 100644 --- a/noxfile.py +++ b/noxfile.py @@ -28,8 +28,8 @@ BLACK_PATHS = ["docs", "google", "tests", "noxfile.py", "setup.py"] DEFAULT_PYTHON_VERSION = "3.8" -SYSTEM_TEST_PYTHON_VERSIONS = ["2.7", "3.8"] -UNIT_TEST_PYTHON_VERSIONS = ["2.7", "3.6", "3.7", "3.8", "3.9", "3.10"] +SYSTEM_TEST_PYTHON_VERSIONS = ["3.8"] +UNIT_TEST_PYTHON_VERSIONS = ["3.6", "3.7", "3.8", "3.9", "3.10"] CONFORMANCE_TEST_PYTHON_VERSIONS = ["3.8"] _DEFAULT_STORAGE_HOST = "https://storage.googleapis.com" diff --git a/setup.py b/setup.py index 0bb36ff93..9264e4f56 100644 --- a/setup.py +++ b/setup.py @@ -28,19 +28,12 @@ # 'Development Status :: 5 - Production/Stable' release_status = "Development Status :: 5 - Production/Stable" dependencies = [ - "google-auth >= 1.25.0, < 2.0dev; python_version<'3.0'", - "google-auth >= 1.25.0, < 3.0dev; python_version>='3.6'", - "google-api-core >= 1.29.0, < 2.0dev; python_version<'3.0'", - "google-api-core >= 1.29.0, < 3.0dev; python_version>='3.6'", - "google-cloud-core >= 1.6.0, < 2.0dev; python_version<'3.0'", - "google-cloud-core >= 1.6.0, < 3.0dev; python_version>='3.6'", - "google-resumable-media >= 1.3.0, < 2.0dev; python_version<'3.0'", - "google-resumable-media >= 1.3.0, < 3.0dev; python_version>='3.6'", + "google-auth >= 1.25.0, < 3.0dev", + "google-api-core >= 1.29.0, < 3.0dev", + "google-cloud-core >= 1.6.0, < 3.0dev", + "google-resumable-media >= 1.3.0", "requests >= 2.18.0, < 3.0.0dev", - "protobuf < 3.18.0; python_version<'3.0'", - "protobuf; python_version>='3.6'", - "googleapis-common-protos < 1.53.0; python_version<'3.0'", - "six", + "protobuf", ] extras = {} @@ -84,8 +77,6 @@ "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", @@ -100,7 +91,7 @@ namespace_packages=namespaces, install_requires=dependencies, extras_require=extras, - python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*", + python_requires=">=3.6", include_package_data=True, zip_safe=False, ) diff --git a/testing/constraints-2.7.txt b/testing/constraints-2.7.txt deleted file mode 100644 index 81996b797..000000000 --- a/testing/constraints-2.7.txt +++ /dev/null @@ -1,2 +0,0 @@ -google-cloud-testutils < 1.0dev - diff --git a/tests/system/_helpers.py b/tests/system/_helpers.py index 184cf9f56..c172129d6 100644 --- a/tests/system/_helpers.py +++ b/tests/system/_helpers.py @@ -14,8 +14,6 @@ import os -import six - from google.api_core import exceptions from test_utils.retry import RetryErrors @@ -27,17 +25,7 @@ retry_429_503 = RetryErrors( [exceptions.TooManyRequests, exceptions.ServiceUnavailable], max_tries=10 ) - -# Work around https://github.com/googleapis/python-test-utils/issues/36 -if six.PY3: - retry_failures = RetryErrors(AssertionError) -else: - - def retry_failures(decorated): # no-op - wrapped = RetryErrors(AssertionError)(decorated) - wrapped.__wrapped__ = decorated - return wrapped - +retry_failures = RetryErrors(AssertionError) user_project = os.environ.get("GOOGLE_CLOUD_TESTS_USER_PROJECT") testing_mtls = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE") == "true" diff --git a/tests/system/test_blob.py b/tests/system/test_blob.py index c530b58ee..b6d5216a7 100644 --- a/tests/system/test_blob.py +++ b/tests/system/test_blob.py @@ -20,7 +20,6 @@ import warnings import pytest -import six import mock from google import resumable_media @@ -33,7 +32,7 @@ def _check_blob_hash(blob, info): md5_hash = blob.md5_hash - if not isinstance(md5_hash, six.binary_type): + if not isinstance(md5_hash, bytes): md5_hash = md5_hash.encode("utf-8") assert md5_hash == info["hash"] diff --git a/tests/system/test_bucket.py b/tests/system/test_bucket.py index a9d638efb..78fa135ff 100644 --- a/tests/system/test_bucket.py +++ b/tests/system/test_bucket.py @@ -15,7 +15,6 @@ import datetime import pytest -import six from google.api_core import exceptions from . import _helpers @@ -430,7 +429,7 @@ def test_bucket_list_blobs_paginated(listable_bucket, listable_filenames): iterator = listable_bucket.list_blobs(max_results=count) page_iter = iterator.pages - page1 = six.next(page_iter) + page1 = next(page_iter) blobs = list(page1) assert len(blobs) == count assert iterator.next_page_token is not None @@ -440,7 +439,7 @@ def test_bucket_list_blobs_paginated(listable_bucket, listable_filenames): # artificially stopping after ``count`` items. iterator.max_results = None - page2 = six.next(page_iter) + page2 = next(page_iter) last_blobs = list(page2) assert len(last_blobs) == truncation_size @@ -459,7 +458,7 @@ def test_bucket_list_blobs_paginated_w_offset(listable_bucket, listable_filename ) page_iter = iterator.pages - page1 = six.next(page_iter) + page1 = next(page_iter) blobs = list(page1) assert len(blobs) == count assert blobs[0].name == desired_files[0] @@ -470,7 +469,7 @@ def test_bucket_list_blobs_paginated_w_offset(listable_bucket, listable_filename # artificially stopping after ``count`` items. iterator.max_results = None - page2 = six.next(page_iter) + page2 = next(page_iter) last_blobs = list(page2) assert len(last_blobs) == truncation_size assert last_blobs[-1].name == desired_files[-1] @@ -489,7 +488,7 @@ def test_bucket_list_blobs_hierarchy_root_level(hierarchy_bucket, hierarchy_file expected_prefixes = set(["parent/"]) iterator = hierarchy_bucket.list_blobs(delimiter="/") - page = six.next(iterator.pages) + page = next(iterator.pages) blobs = list(page) assert [blob.name for blob in blobs] == expected_names @@ -503,7 +502,7 @@ def test_bucket_list_blobs_hierarchy_first_level(hierarchy_bucket, hierarchy_fil expected_prefixes = set(["parent/child/"]) iterator = hierarchy_bucket.list_blobs(delimiter="/", prefix="parent/") - page = six.next(iterator.pages) + page = next(iterator.pages) blobs = list(page) assert [blob.name for blob in blobs] == expected_names @@ -519,7 +518,7 @@ def test_bucket_list_blobs_hierarchy_second_level( expected_prefixes = set(["parent/child/grand/", "parent/child/other/"]) iterator = hierarchy_bucket.list_blobs(delimiter="/", prefix="parent/child/") - page = six.next(iterator.pages) + page = next(iterator.pages) blobs = list(page) assert [blob.name for blob in blobs] == expected_names assert iterator.next_page_token is None @@ -536,7 +535,7 @@ def test_bucket_list_blobs_hierarchy_third_level(hierarchy_bucket, hierarchy_fil expected_prefixes = set() iterator = hierarchy_bucket.list_blobs(delimiter="/", prefix="parent/child/grand/") - page = six.next(iterator.pages) + page = next(iterator.pages) blobs = list(page) assert [blob.name for blob in blobs] == expected_names @@ -554,7 +553,7 @@ def test_bucket_list_blobs_hierarchy_w_include_trailing_delimiter( iterator = hierarchy_bucket.list_blobs( delimiter="/", include_trailing_delimiter=True ) - page = six.next(iterator.pages) + page = next(iterator.pages) blobs = list(page) assert [blob.name for blob in blobs] == expected_names diff --git a/tests/system/test_hmac_key_metadata.py b/tests/system/test_hmac_key_metadata.py index 5c062dbc3..705b1350b 100644 --- a/tests/system/test_hmac_key_metadata.py +++ b/tests/system/test_hmac_key_metadata.py @@ -15,7 +15,6 @@ import datetime import pytest -import six from google.cloud import _helpers as _cloud_helpers @@ -62,7 +61,7 @@ def test_hmac_key_crud(storage_client, scrubbed_hmac_keys, service_account): metadata, secret = storage_client.create_hmac_key(email) hmac_keys_to_delete.append(metadata) - assert isinstance(secret, six.text_type) + assert isinstance(secret, str) assert len(secret) == 40 after_hmac_keys = set(storage_client.list_hmac_keys()) diff --git a/tests/unit/test__http.py b/tests/unit/test__http.py index 3b022e191..fcdb5d1a7 100644 --- a/tests/unit/test__http.py +++ b/tests/unit/test__http.py @@ -62,8 +62,8 @@ def test_extra_headers(self): ) def test_build_api_url_no_extra_query_params(self): - from six.moves.urllib.parse import parse_qsl - from six.moves.urllib.parse import urlsplit + from urllib.parse import parse_qsl + from urllib.parse import urlsplit conn = self._make_one(object()) uri = conn.build_api_url("/foo") @@ -76,8 +76,8 @@ def test_build_api_url_no_extra_query_params(self): self.assertEqual(parms, {}) def test_build_api_url_w_custom_endpoint(self): - from six.moves.urllib.parse import parse_qsl - from six.moves.urllib.parse import urlsplit + from urllib.parse import parse_qsl + from urllib.parse import urlsplit custom_endpoint = "https://foo-storage.googleapis.com" conn = self._make_one(object(), api_endpoint=custom_endpoint) @@ -91,8 +91,8 @@ def test_build_api_url_w_custom_endpoint(self): self.assertEqual(parms, {}) def test_build_api_url_w_extra_query_params(self): - from six.moves.urllib.parse import parse_qsl - from six.moves.urllib.parse import urlsplit + from urllib.parse import parse_qsl + from urllib.parse import urlsplit conn = self._make_one(object()) uri = conn.build_api_url("/foo", {"bar": "baz"}) diff --git a/tests/unit/test__signing.py b/tests/unit/test__signing.py index fbfa6052f..f863460c5 100644 --- a/tests/unit/test__signing.py +++ b/tests/unit/test__signing.py @@ -21,11 +21,10 @@ import json import time import unittest +import urllib.parse import mock import pytest -import six -from six.moves import urllib_parse from . import _read_local_json @@ -45,21 +44,10 @@ def _utc_seconds(when): def _make_cet_timezone(): - try: - from datetime import timezone + from datetime import timezone + from datetime import timedelta - except ImportError: # Python 2.7 - from google.cloud._helpers import _UTC - - class CET(_UTC): - _tzname = "CET" - _utcoffset = datetime.timedelta(hours=1) - - return CET() - else: - from datetime import timedelta - - return timezone(timedelta(hours=1), name="CET") + return timezone(timedelta(hours=1), name="CET") class Test_get_expiration_seconds_v2(unittest.TestCase): @@ -80,12 +68,6 @@ def test_w_expiration_none(self): def test_w_expiration_int(self): self.assertEqual(self._call_fut(123), 123) - def test_w_expiration_long(self): - if not six.PY2: - raise unittest.SkipTest("No long on Python 3+") - - self.assertEqual(self._call_fut(long(123)), 123) # noqa: F821 - def test_w_expiration_naive_datetime(self): expiration_no_tz = datetime.datetime(2004, 8, 19, 0, 0, 0, 0) utc_seconds = _utc_seconds(expiration_no_tz) @@ -368,7 +350,7 @@ def _generate_helper( headers=None, query_parameters=None, ): - from six.moves.urllib.parse import urlencode + from urllib.parse import urlencode resource = "/name/path" credentials = _make_credentials(signer_email="service@example.com") @@ -429,8 +411,8 @@ def _generate_helper( credentials.sign_bytes.assert_called_once_with(string_to_sign.encode("ascii")) - scheme, netloc, path, qs, frag = urllib_parse.urlsplit(url) - expected_scheme, expected_netloc, _, _, _ = urllib_parse.urlsplit( + scheme, netloc, path, qs, frag = urllib.parse.urlsplit(url) + expected_scheme, expected_netloc, _, _, _ = urllib.parse.urlsplit( api_access_endpoint ) self.assertEqual(scheme, expected_scheme) @@ -439,7 +421,7 @@ def _generate_helper( self.assertEqual(frag, "") # Check the URL parameters. - params = dict(urllib_parse.parse_qsl(qs, keep_blank_values=True)) + params = dict(urllib.parse.parse_qsl(qs, keep_blank_values=True)) self.assertEqual(params["GoogleAccessId"], credentials.signer_email) self.assertEqual(params["Expires"], str(expiration)) @@ -571,9 +553,9 @@ def _generate_helper( # Check the mock was called. credentials.sign_bytes.assert_called_once() - scheme, netloc, path, qs, frag = urllib_parse.urlsplit(url) + scheme, netloc, path, qs, frag = urllib.parse.urlsplit(url) - expected_scheme, expected_netloc, _, _, _ = urllib_parse.urlsplit( + expected_scheme, expected_netloc, _, _, _ = urllib.parse.urlsplit( api_access_endpoint ) self.assertEqual(scheme, expected_scheme) @@ -582,7 +564,7 @@ def _generate_helper( self.assertEqual(frag, "") # Check the URL parameters. - params = dict(urllib_parse.parse_qsl(qs, keep_blank_values=True)) + params = dict(urllib.parse.parse_qsl(qs, keep_blank_values=True)) self.assertEqual(params["X-Goog-Algorithm"], "GOOG4-RSA-SHA256") now_date = now.date().strftime("%Y%m%d") diff --git a/tests/unit/test_batch.py b/tests/unit/test_batch.py index f877448f8..89bf583e9 100644 --- a/tests/unit/test_batch.py +++ b/tests/unit/test_batch.py @@ -12,11 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import http.client +from http.client import SERVICE_UNAVAILABLE +from http.client import NO_CONTENT import unittest import mock import requests -from six.moves import http_client def _make_credentials(): @@ -25,7 +27,7 @@ def _make_credentials(): return mock.Mock(spec=google.auth.credentials.Credentials) -def _make_response(status=http_client.OK, content=b"", headers={}): +def _make_response(status=http.client.OK, content=b"", headers={}): response = requests.Response() response.status_code = status response._content = content @@ -348,7 +350,7 @@ def test_finish_nonempty(self): self.assertEqual(response2.json(), {"foo": 1, "bar": 3}) self.assertEqual(response3.headers, {"Content-Length": "0"}) - self.assertEqual(response3.status_code, http_client.NO_CONTENT) + self.assertEqual(response3.status_code, NO_CONTENT) expected_url = "{}/batch/storage/v1".format(batch.API_BASE_URL) http.request.assert_called_once_with( @@ -452,7 +454,7 @@ def test_finish_multipart_response_with_status_failure(self): url = "http://api.example.com/other_api" expected_response = _make_response( - status=http_client.SERVICE_UNAVAILABLE, + status=SERVICE_UNAVAILABLE, headers={"content-type": 'multipart/mixed; boundary="DEADBEEF="'}, ) http = _make_requests_session([expected_response]) @@ -574,11 +576,11 @@ def _unpack_helper(self, response, content): result = list(self._call_fut(response, content)) self.assertEqual(len(result), 3) - self.assertEqual(result[0].status_code, http_client.OK) + self.assertEqual(result[0].status_code, http.client.OK) self.assertEqual(result[0].json(), {u"bar": 2, u"foo": 1}) - self.assertEqual(result[1].status_code, http_client.OK) + self.assertEqual(result[1].status_code, http.client.OK) self.assertEqual(result[1].json(), {u"foo": 1, u"bar": 3}) - self.assertEqual(result[2].status_code, http_client.NO_CONTENT) + self.assertEqual(result[2].status_code, http.client.NO_CONTENT) def test_bytes_headers(self): RESPONSE = {"content-type": b'multipart/mixed; boundary="DEADBEEF="'} diff --git a/tests/unit/test_blob.py b/tests/unit/test_blob.py index 90d27abd6..a70c16e75 100644 --- a/tests/unit/test_blob.py +++ b/tests/unit/test_blob.py @@ -20,12 +20,11 @@ import os import tempfile import unittest +import http.client +from urllib.parse import urlencode import mock import pytest -import six -from six.moves import http_client -from six.moves.urllib.parse import urlencode from google.cloud.storage.retry import ( DEFAULT_RETRY, @@ -81,7 +80,7 @@ def test_ctor_with_encoded_unicode(self): blob = self._make_one(blob_name, bucket=None) unicode_name = u"wet \N{sailboat}" self.assertNotIsInstance(blob.name, bytes) - self.assertIsInstance(blob.name, six.text_type) + self.assertIsInstance(blob.name, str) self.assertEqual(blob.name, unicode_name) def test_ctor_w_encryption_key(self): @@ -455,7 +454,7 @@ def _generate_signed_url_helper( bucket_bound_hostname=None, scheme="http", ): - from six.moves.urllib import parse + from urllib import parse from google.cloud._helpers import UTC from google.cloud.storage._helpers import _bucket_bound_hostname_url from google.cloud.storage.blob import _API_ACCESS_ENDPOINT @@ -1066,7 +1065,7 @@ def test__extract_headers_from_download_gzipped(self): blob = self._make_one(blob_name, bucket=bucket) response = self._mock_requests_response( - http_client.OK, + http.client.OK, headers={ "Content-Type": "application/json", "Content-Language": "ko-kr", @@ -1102,7 +1101,7 @@ def test__extract_headers_from_download_empty(self): blob = self._make_one(blob_name, bucket=bucket) response = self._mock_requests_response( - http_client.OK, + http.client.OK, headers={ "Content-Type": "application/octet-stream", "Content-Language": "en-US", @@ -1139,7 +1138,7 @@ def test__extract_headers_from_download_w_hash_response_header_none(self): blob = self._make_one(blob_name, bucket=bucket, properties=properties) response = self._mock_requests_response( - http_client.OK, + http.client.OK, headers={"X-Goog-Hash": ""}, # { "x": 5 } gzipped content=b"\x1f\x8b\x08\x00\xcfo\x17_\x02\xff\xabVP\xaaP\xb2R0U\xa8\x05\x00\xa1\xcaQ\x93\n\x00\x00\x00", @@ -1156,7 +1155,7 @@ def test__extract_headers_from_download_w_response_headers_not_match(self): blob = self._make_one(blob_name, bucket=bucket) response = self._mock_requests_response( - http_client.OK, + http.client.OK, headers={"X-Goog-Hash": "bogus=4gcgLQ==,"}, # { "x": 5 } gzipped content=b"", @@ -1587,7 +1586,6 @@ def _download_to_filename_helper( self, updated, raw_download, timeout=None, **extra_kwargs ): import os - from google.cloud.storage._helpers import _convert_to_timestamp from google.cloud._testing import _NamedTemporaryFile blob_name = "blob-name" @@ -1616,10 +1614,7 @@ def _download_to_filename_helper( self.assertIsNone(blob.updated) else: mtime = os.path.getmtime(temp.name) - if six.PY2: - updated_time = _convert_to_timestamp(blob.updated) - else: - updated_time = blob.updated.timestamp() + updated_time = blob.updated.timestamp() self.assertEqual(mtime, updated_time) expected_timeout = self._get_default_timeout() if timeout is None else timeout @@ -2276,7 +2271,7 @@ def _do_multipart_success( # Create some mock arguments. if not client: # Create mocks to be checked for doing transport. - transport = self._mock_transport(http_client.OK, {}) + transport = self._mock_transport(http.client.OK, {}) client = mock.Mock(_http=transport, _connection=_Connection, spec=["_http"]) client._connection.API_BASE_URL = "https://storage.googleapis.com" @@ -2462,7 +2457,7 @@ def test__do_multipart_upload_with_generation_not_match(self, mock_get_boundary) @mock.patch(u"google.resumable_media._upload.get_boundary", return_value=b"==0==") def test__do_multipart_upload_with_client(self, mock_get_boundary): - transport = self._mock_transport(http_client.OK, {}) + transport = self._mock_transport(http.client.OK, {}) client = mock.Mock(_http=transport, _connection=_Connection, spec=["_http"]) client._connection.API_BASE_URL = "https://storage.googleapis.com" self._do_multipart_success(mock_get_boundary, client=client) @@ -2537,7 +2532,7 @@ def _initiate_resumable_helper( if not client: # Create mocks to be checked for doing transport. response_headers = {"location": resumable_url} - transport = self._mock_transport(http_client.OK, response_headers) + transport = self._mock_transport(http.client.OK, response_headers) # Create some mock arguments and call the method under test. client = mock.Mock( @@ -2752,7 +2747,7 @@ def test__initiate_resumable_upload_with_predefined_acl(self): def test__initiate_resumable_upload_with_client(self): resumable_url = "http://test.invalid?upload_id=hey-you" response_headers = {"location": resumable_url} - transport = self._mock_transport(http_client.OK, response_headers) + transport = self._mock_transport(http.client.OK, response_headers) client = mock.Mock(_http=transport, _connection=_Connection, spec=[u"_http"]) client._connection.API_BASE_URL = "https://storage.googleapis.com" @@ -2765,7 +2760,7 @@ def _make_resumable_transport( fake_transport = mock.Mock(spec=["request"]) - fake_response1 = self._mock_requests_response(http_client.OK, headers1) + fake_response1 = self._mock_requests_response(http.client.OK, headers1) fake_response2 = self._mock_requests_response( resumable_media.PERMANENT_REDIRECT, headers2 ) @@ -2774,7 +2769,7 @@ def _make_resumable_transport( fake_response3 = resumable_media.DataCorruption(None) else: fake_response3 = self._mock_requests_response( - http_client.OK, headers3, content=json_body.encode("utf-8") + http.client.OK, headers3, content=json_body.encode("utf-8") ) responses = [fake_response1, fake_response2, fake_response3] @@ -3251,7 +3246,7 @@ def test_upload_from_file_failure(self): message = "Someone is already in this spot." response = requests.Response() - response.status_code = http_client.CONFLICT + response.status_code = http.client.CONFLICT response.request = requests.Request("POST", "http://example.com").prepare() side_effect = InvalidResponse(response, message) @@ -3506,7 +3501,7 @@ def _create_resumable_upload_session_helper( # Create mocks to be checked for doing transport. resumable_url = "http://test.invalid?upload_id=clean-up-everybody" response_headers = {"location": resumable_url} - transport = self._mock_transport(http_client.OK, response_headers) + transport = self._mock_transport(http.client.OK, response_headers) if side_effect is not None: transport.request.side_effect = side_effect @@ -3610,7 +3605,7 @@ def test_create_resumable_upload_session_with_failure(self): message = "5-oh-3 woe is me." response = self._mock_requests_response( - status_code=http_client.SERVICE_UNAVAILABLE, headers={} + status_code=http.client.SERVICE_UNAVAILABLE, headers={} ) side_effect = InvalidResponse(response, message) @@ -5674,7 +5669,7 @@ def _call_fut(error): return _raise_from_invalid_response(error) - def _helper(self, message, code=http_client.BAD_REQUEST, reason=None, args=()): + def _helper(self, message, code=http.client.BAD_REQUEST, reason=None, args=()): import requests from google.resumable_media import InvalidResponse @@ -5703,7 +5698,7 @@ def test_w_206_and_args(self): reason = b"Not available" args = ("one", "two") exc_info = self._helper( - message, code=http_client.PARTIAL_CONTENT, reason=reason, args=args + message, code=http.client.PARTIAL_CONTENT, reason=reason, args=args ) expected = "GET http://example.com/: {}: {}".format( reason.decode("utf-8"), (message,) + args diff --git a/tests/unit/test_bucket.py b/tests/unit/test_bucket.py index 321de717c..8bccee19c 100644 --- a/tests/unit/test_bucket.py +++ b/tests/unit/test_bucket.py @@ -3820,7 +3820,7 @@ def _generate_signed_url_helper( bucket_bound_hostname=None, scheme="http", ): - from six.moves.urllib import parse + from urllib import parse from google.cloud._helpers import UTC from google.cloud.storage._helpers import _bucket_bound_hostname_url from google.cloud.storage.blob import _API_ACCESS_ENDPOINT diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 85c4bc5e2..c7abf5b0d 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -13,6 +13,7 @@ # limitations under the License. import base64 +import http.client import io import json import mock @@ -20,9 +21,8 @@ import re import requests import unittest -from six import string_types -from six.moves import http_client -from six.moves.urllib import parse as urlparse +import urllib + from google.api_core import exceptions @@ -74,7 +74,7 @@ def _make_connection(*responses): return mock_conn -def _make_response(status=http_client.OK, content=b"", headers={}): +def _make_response(status=http.client.OK, content=b"", headers={}): response = requests.Response() response.status_code = status response._content = content @@ -83,7 +83,7 @@ def _make_response(status=http_client.OK, content=b"", headers={}): return response -def _make_json_response(data, status=http_client.OK, headers=None): +def _make_json_response(data, status=http.client.OK, headers=None): headers = headers or {} headers["Content-Type"] = "application/json" return _make_response( @@ -318,7 +318,7 @@ def test_get_service_account_email_wo_project(self): method="GET", url=mock.ANY, data=None, headers=mock.ANY, timeout=42 ) _, kwargs = http.request.call_args - scheme, netloc, path, qs, _ = urlparse.urlsplit(kwargs.get("url")) + scheme, netloc, path, qs, _ = urllib.parse.urlsplit(kwargs.get("url")) self.assertEqual("%s://%s" % (scheme, netloc), client._connection.API_BASE_URL) self.assertEqual( path, @@ -356,7 +356,7 @@ def test_get_service_account_email_w_project(self): timeout=self._get_default_timeout(), ) _, kwargs = http.request.call_args - scheme, netloc, path, qs, _ = urlparse.urlsplit(kwargs.get("url")) + scheme, netloc, path, qs, _ = urllib.parse.urlsplit(kwargs.get("url")) self.assertEqual("%s://%s" % (scheme, netloc), client._connection.API_BASE_URL) self.assertEqual( path, @@ -1420,7 +1420,7 @@ def test_download_blob_to_file_with_failure(self): project = "PROJECT" raw_response = requests.Response() - raw_response.status_code = http_client.NOT_FOUND + raw_response.status_code = http.client.NOT_FOUND raw_request = requests.Request("GET", "http://example.com") raw_response.request = raw_request.prepare() grmp_response = InvalidResponse(raw_response) @@ -1576,12 +1576,12 @@ def _download_blob_to_file_helper( headers = {"accept-encoding": "gzip"} if_etag_match = extra_kwargs.get("if_etag_match") if if_etag_match is not None: - if isinstance(if_etag_match, string_types): + if isinstance(if_etag_match, str): if_etag_match = [if_etag_match] headers["If-Match"] = ", ".join(if_etag_match) if_etag_not_match = extra_kwargs.get("if_etag_not_match") if if_etag_not_match is not None: - if isinstance(if_etag_not_match, string_types): + if isinstance(if_etag_not_match, str): if_etag_not_match = [if_etag_not_match] headers["If-None-Match"] = ", ".join(if_etag_not_match) @@ -2012,7 +2012,7 @@ def test_get_hmac_key_metadata_wo_project(self): method="GET", url=mock.ANY, data=None, headers=mock.ANY, timeout=42 ) _, kwargs = http.request.call_args - scheme, netloc, path, qs, _ = urlparse.urlsplit(kwargs.get("url")) + scheme, netloc, path, qs, _ = urllib.parse.urlsplit(kwargs.get("url")) self.assertEqual("%s://%s" % (scheme, netloc), client._connection.API_BASE_URL) self.assertEqual( path, @@ -2067,7 +2067,7 @@ def test_get_hmac_key_metadata_w_project(self): timeout=self._get_default_timeout(), ) _, kwargs = http.request.call_args - scheme, netloc, path, qs, _ = urlparse.urlsplit(kwargs.get("url")) + scheme, netloc, path, qs, _ = urllib.parse.urlsplit(kwargs.get("url")) self.assertEqual("%s://%s" % (scheme, netloc), client._connection.API_BASE_URL) self.assertEqual( path, @@ -2083,7 +2083,7 @@ def test_get_hmac_key_metadata_w_project(self): ] ), ) - parms = dict(urlparse.parse_qsl(qs)) + parms = dict(urllib.parse.parse_qsl(qs)) self.assertEqual(parms["userProject"], USER_PROJECT) def test_get_signed_policy_v4(self): diff --git a/tests/unit/test_retry.py b/tests/unit/test_retry.py index e28c2d038..b985e5c16 100644 --- a/tests/unit/test_retry.py +++ b/tests/unit/test_retry.py @@ -19,14 +19,6 @@ import mock -try: - ConnectionError -except NameError: - _HAS_STDLIB_CONNECTION_ERROR = False -else: - _HAS_STDLIB_CONNECTION_ERROR = True - - class Test_should_retry(unittest.TestCase): def _call_fut(self, exc): from google.cloud.storage import retry @@ -79,9 +71,6 @@ def test_miss_w_stdlib_error(self): exc = ValueError("testing") self.assertFalse(self._call_fut(exc)) - @unittest.skipUnless( - _HAS_STDLIB_CONNECTION_ERROR, "No builtin 'ConnectionError' in Python 2", - ) def test_w_stdlib_connection_error(self): exc = ConnectionError() self.assertTrue(self._call_fut(exc))