From 21ac69a3373452264d520bf7d9695fb1f058fd01 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 15 Jun 2021 14:24:49 -0400 Subject: [PATCH 01/22] fix!: drop support for Python 2.7 and 3.5 Release-As: 2.0.0b1 --- CONTRIBUTING.rst | 32 ++------------------------------ README.rst | 8 ++------ google/api_core/version.py | 2 +- noxfile.py | 5 +++-- owlbot.py | 11 ++++++++--- setup.py | 5 +---- testing/constraints-2.7.txt | 0 7 files changed, 17 insertions(+), 46 deletions(-) delete mode 100644 testing/constraints-2.7.txt diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 03c4abe5..1e933aff 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,4 +1,3 @@ -.. Generated by synthtool. DO NOT EDIT! ############ Contributing ############ @@ -69,7 +68,6 @@ We use `nox `__ to instrument our tests. - To test your changes, run unit tests with ``nox``:: - $ nox -s unit-2.7 $ nox -s unit-3.8 $ ... @@ -80,8 +78,8 @@ We use `nox `__ to instrument our tests. .. note:: - The unit tests and system tests are described in the - ``noxfile.py`` files in each directory. + The unit tests tests are described in the ``noxfile.py`` files + in each directory. .. nox: https://pypi.org/project/nox/ @@ -136,32 +134,6 @@ Exceptions to PEP8: "Function-Under-Test"), which is PEP8-incompliant, but more readable. Some also use a local variable, ``MUT`` (short for "Module-Under-Test"). -******************** -Running System Tests -******************** - -- To run system tests, you can execute:: - - # Run all system tests - $ nox -s system-3.8 - $ nox -s system-2.7 - - # Run a single system test - $ nox -s system-3.8 -- -k - - - .. note:: - - System tests are only configured to run under Python 2.7 and - Python 3.8. For expediency, we do not run them in older versions - of Python 3. - - This alone will not run the tests. You'll need to change some local - auth settings and change some configuration in your project to - run all the tests. - -- System tests will be run against an actual project. You should use local credentials from gcloud when possible. See `Best practices for application authentication `__. Some tests require a service account. For those tests see `Authenticating as a service account `__. - ************* Test Coverage ************* diff --git a/README.rst b/README.rst index 244043ea..ca5b8200 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ Core Library for Google Client Libraries ======================================== -|pypi| |versions| +|pypi| |versions| This library is not meant to stand-alone. Instead it defines common helpers used by all Google API clients. For more information, see the @@ -16,8 +16,4 @@ common helpers used by all Google API clients. For more information, see the Supported Python Versions ------------------------- -Python >= 3.5 - -Deprecated Python Versions --------------------------- -Python == 2.7. Python 2.7 support will be removed on January 1, 2020. +Python >= 3.6 diff --git a/google/api_core/version.py b/google/api_core/version.py index ed763cfa..c9cdad6f 100644 --- a/google/api_core/version.py +++ b/google/api_core/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.30.0" +__version__ = "2.0.0-b1" diff --git a/noxfile.py b/noxfile.py index 650cef28..1e38da79 100644 --- a/noxfile.py +++ b/noxfile.py @@ -24,6 +24,7 @@ _MINIMAL_ASYNCIO_SUPPORT_PYTHON_VERSION = [3, 6] + def _greater_or_equal_than_36(version_string): tokens = version_string.split(".") for i, token in enumerate(tokens): @@ -77,13 +78,13 @@ def default(session): session.run(*pytest_args) -@nox.session(python=["2.7", "3.6", "3.7", "3.8", "3.9"]) +@nox.session(python=["3.6", "3.7", "3.8", "3.9"]) def unit(session): """Run the unit test suite.""" default(session) -@nox.session(python=["2.7", "3.6", "3.7", "3.8", "3.9"]) +@nox.session(python=["3.6", "3.7", "3.8", "3.9"]) def unit_grpc_gcp(session): """Run the unit test suite with grpcio-gcp installed.""" constraints_path = str( diff --git a/owlbot.py b/owlbot.py index 2a525c8e..e5293b13 100644 --- a/owlbot.py +++ b/owlbot.py @@ -14,8 +14,6 @@ """This script is used to synthesize generated parts of this library.""" -import re - import synthtool as s from synthtool import gcp @@ -24,8 +22,15 @@ # ---------------------------------------------------------------------------- # Add templated files # ---------------------------------------------------------------------------- +excludes = [ + "noxfile.py", # pytype + "setup.cfg", # pytype + ".flake8", # flake8-import-order, layout + ".coveragerc", # layout + "CONTRIBUTING.rst", # no systests +] templated_files = common.py_library(cov_level=100) -s.move(templated_files, excludes=["noxfile.py", ".flake8", ".coveragerc", "setup.cfg"]) +s.move(templated_files, excludes=excludes) # Add pytype support s.replace( diff --git a/setup.py b/setup.py index 26fab7e6..f84983bf 100644 --- a/setup.py +++ b/setup.py @@ -86,10 +86,7 @@ "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.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", @@ -102,7 +99,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 e69de29b..00000000 From 78caa92254cdaada04470f108de1f94f9a617847 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 15 Jun 2021 14:41:35 -0400 Subject: [PATCH 02/22] chore: drop 'six' module --- google/api_core/bidi.py | 9 ++- google/api_core/client_options.py | 2 +- google/api_core/exceptions.py | 46 +++++++------- google/api_core/future/base.py | 5 +- google/api_core/gapic_v1/config.py | 15 ++--- google/api_core/gapic_v1/routing_header.py | 3 +- google/api_core/general_helpers.py | 6 +- google/api_core/grpc_helpers.py | 72 +++++++++++----------- google/api_core/page_iterator.py | 9 +-- google/api_core/path_template.py | 6 +- google/api_core/retry.py | 13 ++-- google/api_core/timeout.py | 4 -- setup.py | 1 - tests/unit/test_bidi.py | 23 +++---- tests/unit/test_exceptions.py | 28 ++++----- tests/unit/test_page_iterator.py | 67 ++++++++++---------- 16 files changed, 143 insertions(+), 166 deletions(-) diff --git a/google/api_core/bidi.py b/google/api_core/bidi.py index be52d97d..44114305 100644 --- a/google/api_core/bidi.py +++ b/google/api_core/bidi.py @@ -17,11 +17,10 @@ import collections import datetime import logging +import queue as queue_module import threading import time -from six.moves import queue - from google.api_core import exceptions _LOGGER = logging.getLogger(__name__) @@ -71,7 +70,7 @@ class _RequestQueueGenerator(object): CPU consumed by spinning is pretty minuscule. Args: - queue (queue.Queue): The request queue. + queue (queue_module.Queue): The request queue. period (float): The number of seconds to wait for items from the queue before checking if the RPC is cancelled. In practice, this determines the maximum amount of time the request consumption @@ -108,7 +107,7 @@ def __iter__(self): while True: try: item = self._queue.get(timeout=self._period) - except queue.Empty: + except queue_module.Empty: if not self._is_active(): _LOGGER.debug( "Empty queue and inactive call, exiting request " "generator." @@ -247,7 +246,7 @@ def __init__(self, start_rpc, initial_request=None, metadata=None): self._start_rpc = start_rpc self._initial_request = initial_request self._rpc_metadata = metadata - self._request_queue = queue.Queue() + self._request_queue = queue_module.Queue() self._request_generator = None self._is_active = False self._callbacks = [] diff --git a/google/api_core/client_options.py b/google/api_core/client_options.py index 57000e95..be5523df 100644 --- a/google/api_core/client_options.py +++ b/google/api_core/client_options.py @@ -101,7 +101,7 @@ def from_dict(options): """Construct a client options object from a mapping object. Args: - options (six.moves.collections_abc.Mapping): A mapping object with client options. + options (collections.abc.Mapping): A mapping object with client options. See the docstring for ClientOptions for details on valid arguments. """ diff --git a/google/api_core/exceptions.py b/google/api_core/exceptions.py index 412fc2ee..573eb715 100644 --- a/google/api_core/exceptions.py +++ b/google/api_core/exceptions.py @@ -21,8 +21,7 @@ from __future__ import absolute_import from __future__ import unicode_literals -import six -from six.moves import http_client +import http.client try: import grpc @@ -56,7 +55,6 @@ class DuplicateCredentialArgs(GoogleAPIError): pass -@six.python_2_unicode_compatible class RetryError(GoogleAPIError): """Raised when a function has exhausted all of its available retries. @@ -92,9 +90,7 @@ def __new__(mcs, name, bases, class_dict): return cls -@six.python_2_unicode_compatible -@six.add_metaclass(_GoogleAPICallErrorMeta) -class GoogleAPICallError(GoogleAPIError): +class GoogleAPICallError(GoogleAPIError, metaclass=_GoogleAPICallErrorMeta): """Base class for exceptions raised by calling API methods. Args: @@ -153,25 +149,25 @@ class Redirection(GoogleAPICallError): class MovedPermanently(Redirection): """Exception mapping a ``301 Moved Permanently`` response.""" - code = http_client.MOVED_PERMANENTLY + code = http.client.MOVED_PERMANENTLY class NotModified(Redirection): """Exception mapping a ``304 Not Modified`` response.""" - code = http_client.NOT_MODIFIED + code = http.client.NOT_MODIFIED class TemporaryRedirect(Redirection): """Exception mapping a ``307 Temporary Redirect`` response.""" - code = http_client.TEMPORARY_REDIRECT + code = http.client.TEMPORARY_REDIRECT class ResumeIncomplete(Redirection): """Exception mapping a ``308 Resume Incomplete`` response. - .. note:: :attr:`http_client.PERMANENT_REDIRECT` is ``308``, but Google + .. note:: :attr:`http.client.PERMANENT_REDIRECT` is ``308``, but Google APIs differ in their use of this status code. """ @@ -185,7 +181,7 @@ class ClientError(GoogleAPICallError): class BadRequest(ClientError): """Exception mapping a ``400 Bad Request`` response.""" - code = http_client.BAD_REQUEST + code = http.client.BAD_REQUEST class InvalidArgument(BadRequest): @@ -210,7 +206,7 @@ class OutOfRange(BadRequest): class Unauthorized(ClientError): """Exception mapping a ``401 Unauthorized`` response.""" - code = http_client.UNAUTHORIZED + code = http.client.UNAUTHORIZED class Unauthenticated(Unauthorized): @@ -222,7 +218,7 @@ class Unauthenticated(Unauthorized): class Forbidden(ClientError): """Exception mapping a ``403 Forbidden`` response.""" - code = http_client.FORBIDDEN + code = http.client.FORBIDDEN class PermissionDenied(Forbidden): @@ -235,20 +231,20 @@ class NotFound(ClientError): """Exception mapping a ``404 Not Found`` response or a :attr:`grpc.StatusCode.NOT_FOUND` error.""" - code = http_client.NOT_FOUND + code = http.client.NOT_FOUND grpc_status_code = grpc.StatusCode.NOT_FOUND if grpc is not None else None class MethodNotAllowed(ClientError): """Exception mapping a ``405 Method Not Allowed`` response.""" - code = http_client.METHOD_NOT_ALLOWED + code = http.client.METHOD_NOT_ALLOWED class Conflict(ClientError): """Exception mapping a ``409 Conflict`` response.""" - code = http_client.CONFLICT + code = http.client.CONFLICT class AlreadyExists(Conflict): @@ -266,25 +262,25 @@ class Aborted(Conflict): class LengthRequired(ClientError): """Exception mapping a ``411 Length Required`` response.""" - code = http_client.LENGTH_REQUIRED + code = http.client.LENGTH_REQUIRED class PreconditionFailed(ClientError): """Exception mapping a ``412 Precondition Failed`` response.""" - code = http_client.PRECONDITION_FAILED + code = http.client.PRECONDITION_FAILED class RequestRangeNotSatisfiable(ClientError): """Exception mapping a ``416 Request Range Not Satisfiable`` response.""" - code = http_client.REQUESTED_RANGE_NOT_SATISFIABLE + code = http.client.REQUESTED_RANGE_NOT_SATISFIABLE class TooManyRequests(ClientError): """Exception mapping a ``429 Too Many Requests`` response.""" - # http_client does not define a constant for this in Python 2. + # http.client does not define a constant for this in Python 2. code = 429 @@ -312,7 +308,7 @@ class InternalServerError(ServerError): """Exception mapping a ``500 Internal Server Error`` response. or a :attr:`grpc.StatusCode.INTERNAL` error.""" - code = http_client.INTERNAL_SERVER_ERROR + code = http.client.INTERNAL_SERVER_ERROR grpc_status_code = grpc.StatusCode.INTERNAL if grpc is not None else None @@ -332,28 +328,28 @@ class MethodNotImplemented(ServerError): """Exception mapping a ``501 Not Implemented`` response or a :attr:`grpc.StatusCode.UNIMPLEMENTED` error.""" - code = http_client.NOT_IMPLEMENTED + code = http.client.NOT_IMPLEMENTED grpc_status_code = grpc.StatusCode.UNIMPLEMENTED if grpc is not None else None class BadGateway(ServerError): """Exception mapping a ``502 Bad Gateway`` response.""" - code = http_client.BAD_GATEWAY + code = http.client.BAD_GATEWAY class ServiceUnavailable(ServerError): """Exception mapping a ``503 Service Unavailable`` response or a :attr:`grpc.StatusCode.UNAVAILABLE` error.""" - code = http_client.SERVICE_UNAVAILABLE + code = http.client.SERVICE_UNAVAILABLE grpc_status_code = grpc.StatusCode.UNAVAILABLE if grpc is not None else None class GatewayTimeout(ServerError): """Exception mapping a ``504 Gateway Timeout`` response.""" - code = http_client.GATEWAY_TIMEOUT + code = http.client.GATEWAY_TIMEOUT class DeadlineExceeded(GatewayTimeout): diff --git a/google/api_core/future/base.py b/google/api_core/future/base.py index e7888ca3..f3005860 100644 --- a/google/api_core/future/base.py +++ b/google/api_core/future/base.py @@ -16,11 +16,8 @@ import abc -import six - -@six.add_metaclass(abc.ABCMeta) -class Future(object): +class Future(object, metaclass=abc.ABCMeta): # pylint: disable=missing-docstring # We inherit the interfaces here from concurrent.futures. diff --git a/google/api_core/gapic_v1/config.py b/google/api_core/gapic_v1/config.py index 2a56cf1b..9c722871 100644 --- a/google/api_core/gapic_v1/config.py +++ b/google/api_core/gapic_v1/config.py @@ -21,7 +21,6 @@ import collections import grpc -import six from google.api_core import exceptions from google.api_core import retry @@ -130,30 +129,28 @@ def parse_method_configs(interface_config, retry_impl=retry.Retry): # Grab all the retry codes retry_codes_map = { name: retry_codes - for name, retry_codes in six.iteritems(interface_config.get("retry_codes", {})) + for name, retry_codes in interface_config.get("retry_codes", {}).items() } # Grab all of the retry params retry_params_map = { name: retry_params - for name, retry_params in six.iteritems( - interface_config.get("retry_params", {}) - ) + for name, retry_params in interface_config.get("retry_params", {}).items() } # Iterate through all the API methods and create a flat MethodConfig # instance for each one. method_configs = {} - for method_name, method_params in six.iteritems( - interface_config.get("methods", {}) - ): + for method_name, method_params in interface_config.get("methods", {}).items(): retry_params_name = method_params.get("retry_params_name") if retry_params_name is not None: retry_params = retry_params_map[retry_params_name] retry_ = _retry_from_retry_config( - retry_params, retry_codes_map[method_params["retry_codes_name"]], retry_impl + retry_params, + retry_codes_map[method_params["retry_codes_name"]], + retry_impl, ) timeout_ = _timeout_from_retry_config(retry_params) diff --git a/google/api_core/gapic_v1/routing_header.py b/google/api_core/gapic_v1/routing_header.py index 3fb12a6f..42f45baf 100644 --- a/google/api_core/gapic_v1/routing_header.py +++ b/google/api_core/gapic_v1/routing_header.py @@ -21,8 +21,7 @@ """ import sys - -from six.moves.urllib.parse import urlencode +from urllib.parse import urlencode ROUTING_METADATA_KEY = "x-goog-request-params" diff --git a/google/api_core/general_helpers.py b/google/api_core/general_helpers.py index d2d0c440..33da2381 100644 --- a/google/api_core/general_helpers.py +++ b/google/api_core/general_helpers.py @@ -16,8 +16,6 @@ import functools -import six - # functools.partial objects lack several attributes present on real function # objects. In Python 2 wraps fails on this so use a restricted set instead. @@ -28,6 +26,6 @@ def wraps(wrapped): """A functools.wraps helper that handles partial objects on Python 2.""" # https://github.com/google/pytype/issues/322 if isinstance(wrapped, functools.partial): # pytype: disable=wrong-arg-types - return six.wraps(wrapped, assigned=_PARTIAL_VALID_ASSIGNMENTS) + return functools.wraps(wrapped, assigned=_PARTIAL_VALID_ASSIGNMENTS) else: - return six.wraps(wrapped) + return functools.wraps(wrapped) diff --git a/google/api_core/grpc_helpers.py b/google/api_core/grpc_helpers.py index e8f9ee8a..a232a6aa 100644 --- a/google/api_core/grpc_helpers.py +++ b/google/api_core/grpc_helpers.py @@ -15,10 +15,10 @@ """Helpers for :mod:`grpc`.""" import collections +import functools import grpc import pkg_resources -import six from google.api_core import exceptions from google.api_core import general_helpers @@ -61,12 +61,12 @@ def _wrap_unary_errors(callable_): """Map errors for Unary-Unary and Stream-Unary gRPC callables.""" _patch_callable_name(callable_) - @six.wraps(callable_) + @functools.wraps(callable_) def error_remapped_callable(*args, **kwargs): try: return callable_(*args, **kwargs) except grpc.RpcError as exc: - six.raise_from(exceptions.from_grpc_error(exc), exc) + raise exceptions.from_grpc_error(exc) from exc return error_remapped_callable @@ -80,7 +80,7 @@ def __init__(self, wrapped, prefetch_first_result=True): # to retrieve the first result, in order to fail, in order to trigger a retry. try: if prefetch_first_result: - self._stored_first_result = six.next(self._wrapped) + self._stored_first_result = next(self._wrapped) except TypeError: # It is possible the wrapped method isn't an iterable (a grpc.Call # for instance). If this happens don't store the first result. @@ -104,10 +104,10 @@ def next(self): result = self._stored_first_result del self._stored_first_result return result - return six.next(self._wrapped) + return next(self._wrapped) except grpc.RpcError as exc: # If the stream has already returned data, we cannot recover here. - six.raise_from(exceptions.from_grpc_error(exc), exc) + raise exceptions.from_grpc_error(exc) from exc # Alias needed for Python 2/3 support. __next__ = next @@ -157,9 +157,11 @@ def error_remapped_callable(*args, **kwargs): # hidden flag to see if pre-fetching is disabled. # https://github.com/googleapis/python-pubsub/issues/93#issuecomment-630762257 prefetch_first = getattr(callable_, "_prefetch_first_result_", True) - return _StreamingResponseIterator(result, prefetch_first_result=prefetch_first) + return _StreamingResponseIterator( + result, prefetch_first_result=prefetch_first + ) except grpc.RpcError as exc: - six.raise_from(exceptions.from_grpc_error(exc), exc) + raise exceptions.from_grpc_error(exc) from exc return error_remapped_callable @@ -187,13 +189,14 @@ def wrap_errors(callable_): def _create_composite_credentials( - credentials=None, - credentials_file=None, - default_scopes=None, - scopes=None, - ssl_credentials=None, - quota_project_id=None, - default_host=None): + credentials=None, + credentials_file=None, + default_scopes=None, + scopes=None, + ssl_credentials=None, + quota_project_id=None, + default_host=None, +): """Create the composite credentials for secure channels. Args: @@ -227,20 +230,20 @@ def _create_composite_credentials( if credentials_file: credentials, _ = google.auth.load_credentials_from_file( - credentials_file, - scopes=scopes, - default_scopes=default_scopes + credentials_file, scopes=scopes, default_scopes=default_scopes ) elif credentials: credentials = google.auth.credentials.with_scopes_if_required( - credentials, - scopes=scopes, - default_scopes=default_scopes + credentials, scopes=scopes, default_scopes=default_scopes ) else: - credentials, _ = google.auth.default(scopes=scopes, default_scopes=default_scopes) + credentials, _ = google.auth.default( + scopes=scopes, default_scopes=default_scopes + ) - if quota_project_id and isinstance(credentials, google.auth.credentials.CredentialsWithQuotaProject): + if quota_project_id and isinstance( + credentials, google.auth.credentials.CredentialsWithQuotaProject + ): credentials = credentials.with_quota_project(quota_project_id) request = google.auth.transport.requests.Request() @@ -257,21 +260,20 @@ def _create_composite_credentials( ssl_credentials = grpc.ssl_channel_credentials() # Combine the ssl credentials and the authorization credentials. - return grpc.composite_channel_credentials( - ssl_credentials, google_auth_credentials - ) + return grpc.composite_channel_credentials(ssl_credentials, google_auth_credentials) def create_channel( - target, - credentials=None, - scopes=None, - ssl_credentials=None, - credentials_file=None, - quota_project_id=None, - default_scopes=None, - default_host=None, - **kwargs): + target, + credentials=None, + scopes=None, + ssl_credentials=None, + credentials_file=None, + quota_project_id=None, + default_scopes=None, + default_host=None, + **kwargs +): """Create a secure channel with credentials. Args: diff --git a/google/api_core/page_iterator.py b/google/api_core/page_iterator.py index 49879bc9..6fbd91d8 100644 --- a/google/api_core/page_iterator.py +++ b/google/api_core/page_iterator.py @@ -81,8 +81,6 @@ import abc -import six - class Page(object): """Single page of results in an iterator. @@ -129,7 +127,7 @@ def __iter__(self): def next(self): """Get the next value in the page.""" - item = six.next(self._item_iter) + item = next(self._item_iter) result = self._item_to_value(self._parent, item) # Since we've successfully got the next value from the # iterator, we update the number of remaining. @@ -147,8 +145,7 @@ def _item_to_value_identity(iterator, item): return item -@six.add_metaclass(abc.ABCMeta) -class Iterator(object): +class Iterator(object, metaclass=abc.ABCMeta): """A generic class for iterating through API list responses. Args: @@ -484,7 +481,7 @@ def _next_page(self): there are no pages left. """ try: - items = six.next(self._gax_page_iter) + items = next(self._gax_page_iter) page = Page(self, items, self.item_to_value) self.next_page_token = self._gax_page_iter.page_token or None return page diff --git a/google/api_core/path_template.py b/google/api_core/path_template.py index bb549356..47dcca5b 100644 --- a/google/api_core/path_template.py +++ b/google/api_core/path_template.py @@ -28,8 +28,6 @@ import functools import re -import six - # Regular expression for extracting variable parts from a path template. # The variables can be expressed as: # @@ -83,7 +81,7 @@ def _expand_variable_match(positional_vars, named_vars, match): name = match.group("name") if name is not None: try: - return six.text_type(named_vars[name]) + return str(named_vars[name]) except KeyError: raise ValueError( "Named variable '{}' not specified and needed by template " @@ -91,7 +89,7 @@ def _expand_variable_match(positional_vars, named_vars, match): ) elif positional is not None: try: - return six.text_type(positional_vars.pop(0)) + return str(positional_vars.pop(0)) except IndexError: raise ValueError( "Positional variable not specified and needed by template " diff --git a/google/api_core/retry.py b/google/api_core/retry.py index f0f23bc8..4a5192c3 100644 --- a/google/api_core/retry.py +++ b/google/api_core/retry.py @@ -63,7 +63,6 @@ def check_if_exists(): import time import requests.exceptions -import six from google.api_core import datetime_helpers from google.api_core import exceptions @@ -200,15 +199,12 @@ def retry_target(target, predicate, sleep_generator, deadline, on_error=None): if deadline_datetime is not None: if deadline_datetime <= now: - six.raise_from( - exceptions.RetryError( - "Deadline of {:.1f}s exceeded while calling {}".format( - deadline, target - ), - last_exc, + raise exceptions.RetryError( + "Deadline of {:.1f}s exceeded while calling {}".format( + deadline, target ), last_exc, - ) + ) from last_exc else: time_to_deadline = (deadline_datetime - now).total_seconds() sleep = min(time_to_deadline, sleep) @@ -221,7 +217,6 @@ def retry_target(target, predicate, sleep_generator, deadline, on_error=None): raise ValueError("Sleep generator stopped yielding sleep values.") -@six.python_2_unicode_compatible class Retry(object): """Exponential retry decorator. diff --git a/google/api_core/timeout.py b/google/api_core/timeout.py index 17c1beab..79fddd17 100644 --- a/google/api_core/timeout.py +++ b/google/api_core/timeout.py @@ -55,8 +55,6 @@ def is_thing_ready(timeout=None): import datetime -import six - from google.api_core import datetime_helpers from google.api_core import general_helpers @@ -68,7 +66,6 @@ def is_thing_ready(timeout=None): _DEFAULT_DEADLINE = None -@six.python_2_unicode_compatible class ConstantTimeout(object): """A decorator that adds a constant timeout argument. @@ -140,7 +137,6 @@ def _exponential_timeout_generator(initial, maximum, multiplier, deadline): timeout = timeout * multiplier -@six.python_2_unicode_compatible class ExponentialTimeout(object): """A decorator that adds an exponentially increasing timeout argument. diff --git a/setup.py b/setup.py index f84983bf..755f6d1a 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,6 @@ "requests >= 2.18.0, < 3.0.0dev", "setuptools >= 40.3.0", "packaging >= 14.3", - "six >= 1.13.0", "pytz", 'futures >= 3.2.0; python_version < "3.2"', ] diff --git a/tests/unit/test_bidi.py b/tests/unit/test_bidi.py index 52215cbd..a8436c37 100644 --- a/tests/unit/test_bidi.py +++ b/tests/unit/test_bidi.py @@ -14,12 +14,12 @@ import datetime import logging +import queue import threading import grpc import mock import pytest -from six.moves import queue from google.api_core import bidi from google.api_core import exceptions @@ -121,21 +121,18 @@ class Test_Throttle(object): def test_repr(self): delta = datetime.timedelta(seconds=4.5) instance = bidi._Throttle(access_limit=42, time_window=delta) - assert repr(instance) == \ - "_Throttle(access_limit=42, time_window={})".format(repr(delta)) + assert repr(instance) == "_Throttle(access_limit=42, time_window={})".format( + repr(delta) + ) def test_raises_error_on_invalid_init_arguments(self): with pytest.raises(ValueError) as exc_info: - bidi._Throttle( - access_limit=10, time_window=datetime.timedelta(seconds=0.0) - ) + bidi._Throttle(access_limit=10, time_window=datetime.timedelta(seconds=0.0)) assert "time_window" in str(exc_info.value) assert "must be a positive timedelta" in str(exc_info.value) with pytest.raises(ValueError) as exc_info: - bidi._Throttle( - access_limit=0, time_window=datetime.timedelta(seconds=10) - ) + bidi._Throttle(access_limit=0, time_window=datetime.timedelta(seconds=10)) assert "access_limit" in str(exc_info.value) assert "must be positive" in str(exc_info.value) @@ -461,7 +458,9 @@ def test_send_terminate(self): ) should_recover = mock.Mock(spec=["__call__"], return_value=False) should_terminate = mock.Mock(spec=["__call__"], return_value=True) - bidi_rpc = bidi.ResumableBidiRpc(start_rpc, should_recover, should_terminate=should_terminate) + bidi_rpc = bidi.ResumableBidiRpc( + start_rpc, should_recover, should_terminate=should_terminate + ) bidi_rpc.open() @@ -527,7 +526,9 @@ def test_recv_terminate(self): ) should_recover = mock.Mock(spec=["__call__"], return_value=False) should_terminate = mock.Mock(spec=["__call__"], return_value=True) - bidi_rpc = bidi.ResumableBidiRpc(start_rpc, should_recover, should_terminate=should_terminate) + bidi_rpc = bidi.ResumableBidiRpc( + start_rpc, should_recover, should_terminate=should_terminate + ) bidi_rpc.open() diff --git a/tests/unit/test_exceptions.py b/tests/unit/test_exceptions.py index fb29015f..aa6e508f 100644 --- a/tests/unit/test_exceptions.py +++ b/tests/unit/test_exceptions.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import http.client import json import grpc import mock import requests -from six.moves import http_client from google.api_core import exceptions @@ -50,8 +50,8 @@ def test_create_google_cloud_error_with_args(): def test_from_http_status(): message = "message" - exception = exceptions.from_http_status(http_client.NOT_FOUND, message) - assert exception.code == http_client.NOT_FOUND + exception = exceptions.from_http_status(http.client.NOT_FOUND, message) + assert exception.code == http.client.NOT_FOUND assert exception.message == message assert exception.errors == [] @@ -61,11 +61,11 @@ def test_from_http_status_with_errors_and_response(): errors = ["1", "2"] response = mock.sentinel.response exception = exceptions.from_http_status( - http_client.NOT_FOUND, message, errors=errors, response=response + http.client.NOT_FOUND, message, errors=errors, response=response ) assert isinstance(exception, exceptions.NotFound) - assert exception.code == http_client.NOT_FOUND + assert exception.code == http.client.NOT_FOUND assert exception.message == message assert exception.errors == errors assert exception.response == response @@ -82,7 +82,7 @@ def test_from_http_status_unknown_code(): def make_response(content): response = requests.Response() response._content = content - response.status_code = http_client.NOT_FOUND + response.status_code = http.client.NOT_FOUND response.request = requests.Request( method="POST", url="https://example.com" ).prepare() @@ -95,7 +95,7 @@ def test_from_http_response_no_content(): exception = exceptions.from_http_response(response) assert isinstance(exception, exceptions.NotFound) - assert exception.code == http_client.NOT_FOUND + assert exception.code == http.client.NOT_FOUND assert exception.message == "POST https://example.com/: unknown error" assert exception.response == response @@ -106,7 +106,7 @@ def test_from_http_response_text_content(): exception = exceptions.from_http_response(response) assert isinstance(exception, exceptions.NotFound) - assert exception.code == http_client.NOT_FOUND + assert exception.code == http.client.NOT_FOUND assert exception.message == "POST https://example.com/: message" @@ -120,7 +120,7 @@ def test_from_http_response_json_content(): exception = exceptions.from_http_response(response) assert isinstance(exception, exceptions.NotFound) - assert exception.code == http_client.NOT_FOUND + assert exception.code == http.client.NOT_FOUND assert exception.message == "POST https://example.com/: json message" assert exception.errors == ["1", "2"] @@ -131,7 +131,7 @@ def test_from_http_response_bad_json_content(): exception = exceptions.from_http_response(response) assert isinstance(exception, exceptions.NotFound) - assert exception.code == http_client.NOT_FOUND + assert exception.code == http.client.NOT_FOUND assert exception.message == "POST https://example.com/: unknown error" @@ -145,7 +145,7 @@ def test_from_http_response_json_unicode_content(): exception = exceptions.from_http_response(response) assert isinstance(exception, exceptions.NotFound) - assert exception.code == http_client.NOT_FOUND + assert exception.code == http.client.NOT_FOUND assert exception.message == u"POST https://example.com/: \u2019 message" assert exception.errors == ["1", "2"] @@ -155,7 +155,7 @@ def test_from_grpc_status(): exception = exceptions.from_grpc_status(grpc.StatusCode.OUT_OF_RANGE, message) assert isinstance(exception, exceptions.BadRequest) assert isinstance(exception, exceptions.OutOfRange) - assert exception.code == http_client.BAD_REQUEST + assert exception.code == http.client.BAD_REQUEST assert exception.grpc_status_code == grpc.StatusCode.OUT_OF_RANGE assert exception.message == message assert exception.errors == [] @@ -166,7 +166,7 @@ def test_from_grpc_status_as_int(): exception = exceptions.from_grpc_status(11, message) assert isinstance(exception, exceptions.BadRequest) assert isinstance(exception, exceptions.OutOfRange) - assert exception.code == http_client.BAD_REQUEST + assert exception.code == http.client.BAD_REQUEST assert exception.grpc_status_code == grpc.StatusCode.OUT_OF_RANGE assert exception.message == message assert exception.errors == [] @@ -203,7 +203,7 @@ def test_from_grpc_error(): assert isinstance(exception, exceptions.BadRequest) assert isinstance(exception, exceptions.InvalidArgument) - assert exception.code == http_client.BAD_REQUEST + assert exception.code == http.client.BAD_REQUEST assert exception.grpc_status_code == grpc.StatusCode.INVALID_ARGUMENT assert exception.message == message assert exception.errors == [error] diff --git a/tests/unit/test_page_iterator.py b/tests/unit/test_page_iterator.py index 97b0657b..a44e998b 100644 --- a/tests/unit/test_page_iterator.py +++ b/tests/unit/test_page_iterator.py @@ -17,7 +17,6 @@ import mock import pytest -import six from google.api_core import page_iterator @@ -56,17 +55,17 @@ def test_iterator_calls_parent_item_to_value(self): assert item_to_value.call_count == 0 assert page.remaining == 100 - assert six.next(page) == 10 + assert next(page) == 10 assert item_to_value.call_count == 1 item_to_value.assert_called_with(parent, 10) assert page.remaining == 99 - assert six.next(page) == 11 + assert next(page) == 11 assert item_to_value.call_count == 2 item_to_value.assert_called_with(parent, 11) assert page.remaining == 98 - assert six.next(page) == 12 + assert next(page) == 12 assert item_to_value.call_count == 3 item_to_value.assert_called_with(parent, 12) assert page.remaining == 97 @@ -150,7 +149,8 @@ def test_pages_property_restart(self): def test__page_iter_increment(self): iterator = PageIteratorImpl(None, None) page = page_iterator.Page( - iterator, ("item",), page_iterator._item_to_value_identity) + iterator, ("item",), page_iterator._item_to_value_identity + ) iterator._next_page = mock.Mock(side_effect=[page, None]) assert iterator.num_results == 0 @@ -180,9 +180,11 @@ def test__items_iter(self): # Make pages from mock responses parent = mock.sentinel.parent page1 = page_iterator.Page( - parent, (item1, item2), page_iterator._item_to_value_identity) + parent, (item1, item2), page_iterator._item_to_value_identity + ) page2 = page_iterator.Page( - parent, (item3,), page_iterator._item_to_value_identity) + parent, (item3,), page_iterator._item_to_value_identity + ) iterator = PageIteratorImpl(None, None) iterator._next_page = mock.Mock(side_effect=[page1, page2, None]) @@ -194,17 +196,17 @@ def test__items_iter(self): # Consume items and check the state of the iterator. assert iterator.num_results == 0 - assert six.next(items_iter) == item1 + assert next(items_iter) == item1 assert iterator.num_results == 1 - assert six.next(items_iter) == item2 + assert next(items_iter) == item2 assert iterator.num_results == 2 - assert six.next(items_iter) == item3 + assert next(items_iter) == item3 assert iterator.num_results == 3 with pytest.raises(StopIteration): - six.next(items_iter) + next(items_iter) def test___iter__(self): iterator = PageIteratorImpl(None, None) @@ -286,16 +288,16 @@ def test_iterate(self): items_iter = iter(iterator) - val1 = six.next(items_iter) + val1 = next(items_iter) assert val1 == item1 assert iterator.num_results == 1 - val2 = six.next(items_iter) + val2 = next(items_iter) assert val2 == item2 assert iterator.num_results == 2 with pytest.raises(StopIteration): - six.next(items_iter) + next(items_iter) api_request.assert_called_once_with(method="GET", path=path, query_params={}) @@ -456,7 +458,8 @@ def test__get_next_page_bad_http_method(self): @pytest.mark.parametrize( "page_size,max_results,pages", - [(3, None, False), (3, 8, False), (3, None, True), (3, 8, True)]) + [(3, None, False), (3, 8, False), (3, None, True), (3, 8, True)], + ) def test_page_size_items(self, page_size, max_results, pages): path = "/foo" NITEMS = 10 @@ -467,19 +470,20 @@ def api_request(*args, **kw): assert not args query_params = dict( maxResults=( - page_size if max_results is None - else min(page_size, max_results - n[0])) + page_size + if max_results is None + else min(page_size, max_results - n[0]) + ) ) if n[0]: - query_params.update(pageToken='test') - assert kw == {'method': 'GET', 'path': '/foo', - 'query_params': query_params} - n_items = min(kw['query_params']['maxResults'], NITEMS - n[0]) + query_params.update(pageToken="test") + assert kw == {"method": "GET", "path": "/foo", "query_params": query_params} + n_items = min(kw["query_params"]["maxResults"], NITEMS - n[0]) items = [dict(name=str(i + n[0])) for i in range(n_items)] n[0] += n_items result = dict(items=items) if n[0] < NITEMS: - result.update(nextPageToken='test') + result.update(nextPageToken="test") return result iterator = page_iterator.HTTPIterator( @@ -498,21 +502,20 @@ def api_request(*args, **kw): items_iter = iter(iterator.pages) npages = int(math.ceil(float(n_results) / page_size)) for ipage in range(npages): - assert ( - list(six.next(items_iter)) == [ - dict(name=str(i)) - for i in range(ipage * page_size, - min((ipage + 1) * page_size, n_results), - ) - ]) + assert list(next(items_iter)) == [ + dict(name=str(i)) + for i in range( + ipage * page_size, min((ipage + 1) * page_size, n_results), + ) + ] else: items_iter = iter(iterator) for i in range(n_results): - assert six.next(items_iter) == dict(name=str(i)) + assert next(items_iter) == dict(name=str(i)) assert iterator.num_results == i + 1 with pytest.raises(StopIteration): - six.next(items_iter) + next(items_iter) class TestGRPCIterator(object): @@ -617,7 +620,7 @@ def __init__(self, pages, page_token=None): self.page_token = page_token def next(self): - return six.next(self._pages) + return next(self._pages) __next__ = next From 35c82169521eb0154530774685379425ee4d835e Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 15 Jun 2021 15:24:58 -0400 Subject: [PATCH 03/22] chore: drop 'u"' prefixes for text --- tests/unit/test_exceptions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_exceptions.py b/tests/unit/test_exceptions.py index aa6e508f..10599457 100644 --- a/tests/unit/test_exceptions.py +++ b/tests/unit/test_exceptions.py @@ -138,7 +138,7 @@ def test_from_http_response_bad_json_content(): def test_from_http_response_json_unicode_content(): response = make_response( json.dumps( - {"error": {"message": u"\u2019 message", "errors": ["1", "2"]}} + {"error": {"message": "\u2019 message", "errors": ["1", "2"]}} ).encode("utf-8") ) @@ -146,7 +146,7 @@ def test_from_http_response_json_unicode_content(): assert isinstance(exception, exceptions.NotFound) assert exception.code == http.client.NOT_FOUND - assert exception.message == u"POST https://example.com/: \u2019 message" + assert exception.message == "POST https://example.com/: \u2019 message" assert exception.errors == ["1", "2"] From 35ba7d3ea417fcc48ebd688180904b4eb0b9969b Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 15 Jun 2021 15:30:01 -0400 Subject: [PATCH 04/22] chore: remove other Python 2.7 workarounds --- google/api_core/gapic_v1/__init__.py | 19 ++++++++++--------- google/api_core/gapic_v1/routing_header.py | 4 ---- google/api_core/iam.py | 13 +++---------- google/api_core/operations_v1/__init__.py | 8 ++------ google/api_core/protobuf_helpers.py | 20 ++++++++------------ 5 files changed, 23 insertions(+), 41 deletions(-) diff --git a/google/api_core/gapic_v1/__init__.py b/google/api_core/gapic_v1/__init__.py index ed95da13..e5b7ad35 100644 --- a/google/api_core/gapic_v1/__init__.py +++ b/google/api_core/gapic_v1/__init__.py @@ -12,17 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys - from google.api_core.gapic_v1 import client_info from google.api_core.gapic_v1 import config +from google.api_core.gapic_v1 import config_async from google.api_core.gapic_v1 import method +from google.api_core.gapic_v1 import method_async from google.api_core.gapic_v1 import routing_header -__all__ = ["client_info", "config", "method", "routing_header"] - -if sys.version_info >= (3, 6): - from google.api_core.gapic_v1 import config_async # noqa: F401 - from google.api_core.gapic_v1 import method_async # noqa: F401 - __all__.append("config_async") - __all__.append("method_async") +__all__ = [ + "client_info", + "config", + "config_async", + "method", + "method_async", + "routing_header", +] diff --git a/google/api_core/gapic_v1/routing_header.py b/google/api_core/gapic_v1/routing_header.py index 42f45baf..a7bcb5a8 100644 --- a/google/api_core/gapic_v1/routing_header.py +++ b/google/api_core/gapic_v1/routing_header.py @@ -20,7 +20,6 @@ Generally, these headers are specified as gRPC metadata. """ -import sys from urllib.parse import urlencode ROUTING_METADATA_KEY = "x-goog-request-params" @@ -36,9 +35,6 @@ def to_routing_header(params): Returns: str: The routing header string. """ - if sys.version_info[0] < 3: - # Python 2 does not have the "safe" parameter for urlencode. - return urlencode(params).replace("%2F", "/") return urlencode( params, # Per Google API policy (go/api-url-encoding), / is not encoded. diff --git a/google/api_core/iam.py b/google/api_core/iam.py index c498c685..0dff2271 100644 --- a/google/api_core/iam.py +++ b/google/api_core/iam.py @@ -52,14 +52,10 @@ """ import collections +import collections.abc import operator import warnings -try: - from collections import abc as collections_abc -except ImportError: # Python 2.7 - import collections as collections_abc - # Generic IAM roles OWNER_ROLE = "roles/owner" @@ -87,7 +83,7 @@ class InvalidOperationException(Exception): pass -class Policy(collections_abc.MutableMapping): +class Policy(collections.abc.MutableMapping): """IAM Policy Args: @@ -450,10 +446,7 @@ def to_api_repr(self): for binding in self._bindings: members = binding.get("members") if members: - new_binding = { - "role": binding["role"], - "members": sorted(members) - } + new_binding = {"role": binding["role"], "members": sorted(members)} condition = binding.get("condition") if condition: new_binding["condition"] = condition diff --git a/google/api_core/operations_v1/__init__.py b/google/api_core/operations_v1/__init__.py index bc9befcb..d7f963e2 100644 --- a/google/api_core/operations_v1/__init__.py +++ b/google/api_core/operations_v1/__init__.py @@ -14,11 +14,7 @@ """Package for interacting with the google.longrunning.operations meta-API.""" -import sys - +from google.api_core.operations_v1.operations_async_client import OperationsAsyncClient from google.api_core.operations_v1.operations_client import OperationsClient -__all__ = ["OperationsClient"] -if sys.version_info >= (3, 6, 0): - from google.api_core.operations_v1.operations_async_client import OperationsAsyncClient # noqa: F401 - __all__.append("OperationsAsyncClient") +__all__ = ["OperationsAsyncClient", "OperationsClient"] diff --git a/google/api_core/protobuf_helpers.py b/google/api_core/protobuf_helpers.py index 365ef25c..24d4363f 100644 --- a/google/api_core/protobuf_helpers.py +++ b/google/api_core/protobuf_helpers.py @@ -15,6 +15,7 @@ """Helpers for :mod:`protobuf`.""" import collections +import collections.abc import copy import inspect @@ -22,11 +23,6 @@ from google.protobuf import message from google.protobuf import wrappers_pb2 -try: - from collections import abc as collections_abc -except ImportError: # Python 2.7 - import collections as collections_abc - _SENTINEL = object() _WRAPPER_TYPES = ( @@ -179,7 +175,7 @@ def get(msg_or_dict, key, default=_SENTINEL): # If we get something else, complain. if isinstance(msg_or_dict, message.Message): answer = getattr(msg_or_dict, key, default) - elif isinstance(msg_or_dict, collections_abc.Mapping): + elif isinstance(msg_or_dict, collections.abc.Mapping): answer = msg_or_dict.get(key, default) else: raise TypeError( @@ -204,7 +200,7 @@ def _set_field_on_message(msg, key, value): """Set helper for protobuf Messages.""" # Attempt to set the value on the types of objects we know how to deal # with. - if isinstance(value, (collections_abc.MutableSequence, tuple)): + if isinstance(value, (collections.abc.MutableSequence, tuple)): # Clear the existing repeated protobuf message of any elements # currently inside it. while getattr(msg, key): @@ -212,13 +208,13 @@ def _set_field_on_message(msg, key, value): # Write our new elements to the repeated field. for item in value: - if isinstance(item, collections_abc.Mapping): + if isinstance(item, collections.abc.Mapping): getattr(msg, key).add(**item) else: # protobuf's RepeatedCompositeContainer doesn't support # append. getattr(msg, key).extend([item]) - elif isinstance(value, collections_abc.Mapping): + elif isinstance(value, collections.abc.Mapping): # Assign the dictionary values to the protobuf message. for item_key, item_value in value.items(): set(getattr(msg, key), item_key, item_value) @@ -241,7 +237,7 @@ def set(msg_or_dict, key, value): TypeError: If ``msg_or_dict`` is not a Message or dictionary. """ # Sanity check: Is our target object valid? - if not isinstance(msg_or_dict, (collections_abc.MutableMapping, message.Message)): + if not isinstance(msg_or_dict, (collections.abc.MutableMapping, message.Message)): raise TypeError( "set() expected a dict or protobuf message, got {!r}.".format( type(msg_or_dict) @@ -254,12 +250,12 @@ def set(msg_or_dict, key, value): # If a subkey exists, then get that object and call this method # recursively against it using the subkey. if subkey is not None: - if isinstance(msg_or_dict, collections_abc.MutableMapping): + if isinstance(msg_or_dict, collections.abc.MutableMapping): msg_or_dict.setdefault(basekey, {}) set(get(msg_or_dict, basekey), subkey, value) return - if isinstance(msg_or_dict, collections_abc.MutableMapping): + if isinstance(msg_or_dict, collections.abc.MutableMapping): msg_or_dict[key] = value else: _set_field_on_message(msg_or_dict, key, value) From 61490f5e9c6f3ba4729e35cd1fc8435c82a43041 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 15 Jun 2021 15:53:15 -0400 Subject: [PATCH 05/22] chore: drop use of 'pytz' Closes #74. --- google/api_core/datetime_helpers.py | 20 ++++++----- setup.py | 1 - tests/unit/test_datetime_helpers.py | 51 ++++++++++++++++------------- 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/google/api_core/datetime_helpers.py b/google/api_core/datetime_helpers.py index e52fb1dd..78268efc 100644 --- a/google/api_core/datetime_helpers.py +++ b/google/api_core/datetime_helpers.py @@ -18,12 +18,10 @@ import datetime import re -import pytz - from google.protobuf import timestamp_pb2 -_UTC_EPOCH = datetime.datetime.utcfromtimestamp(0).replace(tzinfo=pytz.utc) +_UTC_EPOCH = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc) _RFC3339_MICROS = "%Y-%m-%dT%H:%M:%S.%fZ" _RFC3339_NO_FRACTION = "%Y-%m-%dT%H:%M:%S" # datetime.strptime cannot handle nanosecond precision: parse w/ regex @@ -83,9 +81,9 @@ def to_microseconds(value): int: Microseconds since the unix epoch. """ if not value.tzinfo: - value = value.replace(tzinfo=pytz.utc) + value = value.replace(tzinfo=datetime.timezone.utc) # Regardless of what timezone is on the value, convert it to UTC. - value = value.astimezone(pytz.utc) + value = value.astimezone(datetime.timezone.utc) # Convert the datetime to a microsecond timestamp. return int(calendar.timegm(value.timetuple()) * 1e6) + value.microsecond @@ -156,7 +154,7 @@ def from_rfc3339(value): nanos = int(fraction) * (10 ** scale) micros = nanos // 1000 - return bare_seconds.replace(microsecond=micros, tzinfo=pytz.utc) + return bare_seconds.replace(microsecond=micros, tzinfo=datetime.timezone.utc) from_rfc3339_nanos = from_rfc3339 # from_rfc3339_nanos method was deprecated. @@ -256,7 +254,7 @@ def from_rfc3339(cls, stamp): bare.minute, bare.second, nanosecond=nanos, - tzinfo=pytz.UTC, + tzinfo=datetime.timezone.utc, ) def timestamp_pb(self): @@ -265,7 +263,11 @@ def timestamp_pb(self): Returns: (:class:`~google.protobuf.timestamp_pb2.Timestamp`): Timestamp message """ - inst = self if self.tzinfo is not None else self.replace(tzinfo=pytz.UTC) + inst = ( + self + if self.tzinfo is not None + else self.replace(tzinfo=datetime.timezone.utc) + ) delta = inst - _UTC_EPOCH seconds = int(delta.total_seconds()) nanos = self._nanosecond or self.microsecond * 1000 @@ -292,5 +294,5 @@ def from_timestamp_pb(cls, stamp): bare.minute, bare.second, nanosecond=stamp.nanos, - tzinfo=pytz.UTC, + tzinfo=datetime.timezone.utc, ) diff --git a/setup.py b/setup.py index 755f6d1a..d4e9a3fa 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,6 @@ "requests >= 2.18.0, < 3.0.0dev", "setuptools >= 40.3.0", "packaging >= 14.3", - "pytz", 'futures >= 3.2.0; python_version < "3.2"', ] extras = { diff --git a/tests/unit/test_datetime_helpers.py b/tests/unit/test_datetime_helpers.py index 4ddcf361..5f5470a6 100644 --- a/tests/unit/test_datetime_helpers.py +++ b/tests/unit/test_datetime_helpers.py @@ -16,7 +16,6 @@ import datetime import pytest -import pytz from google.api_core import datetime_helpers from google.protobuf import timestamp_pb2 @@ -31,7 +30,7 @@ def test_utcnow(): def test_to_milliseconds(): - dt = datetime.datetime(1970, 1, 1, 0, 0, 1, tzinfo=pytz.utc) + dt = datetime.datetime(1970, 1, 1, 0, 0, 1, tzinfo=datetime.timezone.utc) assert datetime_helpers.to_milliseconds(dt) == 1000 @@ -42,7 +41,7 @@ def test_to_microseconds(): def test_to_microseconds_non_utc(): - zone = pytz.FixedOffset(-1) + zone = datetime.timezone(datetime.timedelta(minutes=-1)) dt = datetime.datetime(1970, 1, 1, 0, 0, 0, tzinfo=zone) assert datetime_helpers.to_microseconds(dt) == ONE_MINUTE_IN_MICROSECONDS @@ -56,7 +55,7 @@ def test_to_microseconds_naive(): def test_from_microseconds(): five_mins_from_epoch_in_microseconds = 5 * ONE_MINUTE_IN_MICROSECONDS five_mins_from_epoch_datetime = datetime.datetime( - 1970, 1, 1, 0, 5, 0, tzinfo=pytz.utc + 1970, 1, 1, 0, 5, 0, tzinfo=datetime.timezone.utc ) result = datetime_helpers.from_microseconds(five_mins_from_epoch_in_microseconds) @@ -78,28 +77,28 @@ def test_from_iso8601_time(): def test_from_rfc3339(): value = "2009-12-17T12:44:32.123456Z" assert datetime_helpers.from_rfc3339(value) == datetime.datetime( - 2009, 12, 17, 12, 44, 32, 123456, pytz.utc + 2009, 12, 17, 12, 44, 32, 123456, datetime.timezone.utc ) def test_from_rfc3339_nanos(): value = "2009-12-17T12:44:32.123456Z" assert datetime_helpers.from_rfc3339_nanos(value) == datetime.datetime( - 2009, 12, 17, 12, 44, 32, 123456, pytz.utc + 2009, 12, 17, 12, 44, 32, 123456, datetime.timezone.utc ) def test_from_rfc3339_without_nanos(): value = "2009-12-17T12:44:32Z" assert datetime_helpers.from_rfc3339(value) == datetime.datetime( - 2009, 12, 17, 12, 44, 32, 0, pytz.utc + 2009, 12, 17, 12, 44, 32, 0, datetime.timezone.utc ) def test_from_rfc3339_nanos_without_nanos(): value = "2009-12-17T12:44:32Z" assert datetime_helpers.from_rfc3339_nanos(value) == datetime.datetime( - 2009, 12, 17, 12, 44, 32, 0, pytz.utc + 2009, 12, 17, 12, 44, 32, 0, datetime.timezone.utc ) @@ -119,7 +118,7 @@ def test_from_rfc3339_nanos_without_nanos(): def test_from_rfc3339_with_truncated_nanos(truncated, micros): value = "2009-12-17T12:44:32.{}Z".format(truncated) assert datetime_helpers.from_rfc3339(value) == datetime.datetime( - 2009, 12, 17, 12, 44, 32, micros, pytz.utc + 2009, 12, 17, 12, 44, 32, micros, datetime.timezone.utc ) @@ -148,7 +147,7 @@ def test_from_rfc3339_nanos_is_deprecated(): def test_from_rfc3339_nanos_with_truncated_nanos(truncated, micros): value = "2009-12-17T12:44:32.{}Z".format(truncated) assert datetime_helpers.from_rfc3339_nanos(value) == datetime.datetime( - 2009, 12, 17, 12, 44, 32, micros, pytz.utc + 2009, 12, 17, 12, 44, 32, micros, datetime.timezone.utc ) @@ -171,20 +170,20 @@ def test_to_rfc3339(): def test_to_rfc3339_with_utc(): - value = datetime.datetime(2016, 4, 5, 13, 30, 0, tzinfo=pytz.utc) + value = datetime.datetime(2016, 4, 5, 13, 30, 0, tzinfo=datetime.timezone.utc) expected = "2016-04-05T13:30:00.000000Z" assert datetime_helpers.to_rfc3339(value, ignore_zone=False) == expected def test_to_rfc3339_with_non_utc(): - zone = pytz.FixedOffset(-60) + zone = datetime.timezone(datetime.timedelta(minutes=-60)) value = datetime.datetime(2016, 4, 5, 13, 30, 0, tzinfo=zone) expected = "2016-04-05T14:30:00.000000Z" assert datetime_helpers.to_rfc3339(value, ignore_zone=False) == expected def test_to_rfc3339_with_non_utc_ignore_zone(): - zone = pytz.FixedOffset(-60) + zone = datetime.timezone(datetime.timedelta(minutes=-60)) value = datetime.datetime(2016, 4, 5, 13, 30, 0, tzinfo=zone) expected = "2016-04-05T13:30:00.000000Z" assert datetime_helpers.to_rfc3339(value, ignore_zone=True) == expected @@ -283,7 +282,7 @@ def test_from_rfc3339_w_invalid(): def test_from_rfc3339_wo_fraction(): timestamp = "2016-12-20T21:13:47Z" expected = datetime_helpers.DatetimeWithNanoseconds( - 2016, 12, 20, 21, 13, 47, tzinfo=pytz.UTC + 2016, 12, 20, 21, 13, 47, tzinfo=datetime.timezone.utc ) stamp = datetime_helpers.DatetimeWithNanoseconds.from_rfc3339(timestamp) assert stamp == expected @@ -292,7 +291,7 @@ def test_from_rfc3339_wo_fraction(): def test_from_rfc3339_w_partial_precision(): timestamp = "2016-12-20T21:13:47.1Z" expected = datetime_helpers.DatetimeWithNanoseconds( - 2016, 12, 20, 21, 13, 47, microsecond=100000, tzinfo=pytz.UTC + 2016, 12, 20, 21, 13, 47, microsecond=100000, tzinfo=datetime.timezone.utc ) stamp = datetime_helpers.DatetimeWithNanoseconds.from_rfc3339(timestamp) assert stamp == expected @@ -301,7 +300,7 @@ def test_from_rfc3339_w_partial_precision(): def test_from_rfc3339_w_full_precision(): timestamp = "2016-12-20T21:13:47.123456789Z" expected = datetime_helpers.DatetimeWithNanoseconds( - 2016, 12, 20, 21, 13, 47, nanosecond=123456789, tzinfo=pytz.UTC + 2016, 12, 20, 21, 13, 47, nanosecond=123456789, tzinfo=datetime.timezone.utc ) stamp = datetime_helpers.DatetimeWithNanoseconds.from_rfc3339(timestamp) assert stamp == expected @@ -332,7 +331,9 @@ def test_timestamp_pb_wo_nanos_naive(): stamp = datetime_helpers.DatetimeWithNanoseconds( 2016, 12, 20, 21, 13, 47, 123456 ) - delta = stamp.replace(tzinfo=pytz.UTC) - datetime_helpers._UTC_EPOCH + delta = ( + stamp.replace(tzinfo=datetime.timezone.utc) - datetime_helpers._UTC_EPOCH + ) seconds = int(delta.total_seconds()) nanos = 123456000 timestamp = timestamp_pb2.Timestamp(seconds=seconds, nanos=nanos) @@ -341,7 +342,7 @@ def test_timestamp_pb_wo_nanos_naive(): @staticmethod def test_timestamp_pb_w_nanos(): stamp = datetime_helpers.DatetimeWithNanoseconds( - 2016, 12, 20, 21, 13, 47, nanosecond=123456789, tzinfo=pytz.UTC + 2016, 12, 20, 21, 13, 47, nanosecond=123456789, tzinfo=datetime.timezone.utc ) delta = stamp - datetime_helpers._UTC_EPOCH timestamp = timestamp_pb2.Timestamp( @@ -351,7 +352,9 @@ def test_timestamp_pb_w_nanos(): @staticmethod def test_from_timestamp_pb_wo_nanos(): - when = datetime.datetime(2016, 12, 20, 21, 13, 47, 123456, tzinfo=pytz.UTC) + when = datetime.datetime( + 2016, 12, 20, 21, 13, 47, 123456, tzinfo=datetime.timezone.utc + ) delta = when - datetime_helpers._UTC_EPOCH seconds = int(delta.total_seconds()) timestamp = timestamp_pb2.Timestamp(seconds=seconds) @@ -361,11 +364,13 @@ def test_from_timestamp_pb_wo_nanos(): assert _to_seconds(when) == _to_seconds(stamp) assert stamp.microsecond == 0 assert stamp.nanosecond == 0 - assert stamp.tzinfo == pytz.UTC + assert stamp.tzinfo == datetime.timezone.utc @staticmethod def test_from_timestamp_pb_w_nanos(): - when = datetime.datetime(2016, 12, 20, 21, 13, 47, 123456, tzinfo=pytz.UTC) + when = datetime.datetime( + 2016, 12, 20, 21, 13, 47, 123456, tzinfo=datetime.timezone.utc + ) delta = when - datetime_helpers._UTC_EPOCH seconds = int(delta.total_seconds()) timestamp = timestamp_pb2.Timestamp(seconds=seconds, nanos=123456789) @@ -375,7 +380,7 @@ def test_from_timestamp_pb_w_nanos(): assert _to_seconds(when) == _to_seconds(stamp) assert stamp.microsecond == 123456 assert stamp.nanosecond == 123456789 - assert stamp.tzinfo == pytz.UTC + assert stamp.tzinfo == datetime.timezone.utc def _to_seconds(value): @@ -387,5 +392,5 @@ def _to_seconds(value): Returns: int: Microseconds since the unix epoch. """ - assert value.tzinfo is pytz.UTC + assert value.tzinfo is datetime.timezone.utc return calendar.timegm(value.timetuple()) From 6e21b01d649daa3af9142658bc2c52c7ab12abaf Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 16 Jun 2021 13:52:50 -0400 Subject: [PATCH 06/22] tests: scrub more 2.7 / six constraints --- testing/constraints-2.7.txt | 1 - testing/constraints-3.6.txt | 1 - 2 files changed, 2 deletions(-) delete mode 100644 testing/constraints-2.7.txt diff --git a/testing/constraints-2.7.txt b/testing/constraints-2.7.txt deleted file mode 100644 index 246c89d5..00000000 --- a/testing/constraints-2.7.txt +++ /dev/null @@ -1 +0,0 @@ -googleapis-common-protos >= 1.6.0, < 1.53dev diff --git a/testing/constraints-3.6.txt b/testing/constraints-3.6.txt index ff5b4a7f..2544e8e5 100644 --- a/testing/constraints-3.6.txt +++ b/testing/constraints-3.6.txt @@ -11,7 +11,6 @@ google-auth==1.25.0 requests==2.18.0 setuptools==40.3.0 packaging==14.3 -six==1.13.0 grpcio==1.29.0 grpcio-gcp==0.2.2 grpcio-gcp==0.2.2 From fe7d454a96e024db3524791b44f16f1c6ec11258 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 16 Jun 2021 13:55:33 -0400 Subject: [PATCH 07/22] docs: add section on unspported Python versions to README --- README.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.rst b/README.rst index ca5b8200..dde28796 100644 --- a/README.rst +++ b/README.rst @@ -17,3 +17,12 @@ common helpers used by all Google API clients. For more information, see the Supported Python Versions ------------------------- Python >= 3.6 + + +Unsupported Python Versions +--------------------------- + +Python == 2.7, Python == 3.5. + +The last version of this library compatible with Python 2.7 and 3.5 is +`google-api_core==1.30.0`. From d77fbb004e99febcac94d253cb3d636ea40620c8 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 16 Jun 2021 14:30:44 -0400 Subject: [PATCH 08/22] chore: drop workaround for Python 2.7 --- google/api_core/exceptions.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/google/api_core/exceptions.py b/google/api_core/exceptions.py index 573eb715..13be917e 100644 --- a/google/api_core/exceptions.py +++ b/google/api_core/exceptions.py @@ -280,8 +280,7 @@ class RequestRangeNotSatisfiable(ClientError): class TooManyRequests(ClientError): """Exception mapping a ``429 Too Many Requests`` response.""" - # http.client does not define a constant for this in Python 2. - code = 429 + code = http.client.TOO_MANY_REQUESTS class ResourceExhausted(TooManyRequests): From 440e571b85862382d6c847635cdea9e97578c41d Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 16 Jun 2021 14:31:46 -0400 Subject: [PATCH 09/22] chore: remove 'general_helpers.wraps' Except for a backward-compatibility import, 'functools.wraps' does everything wee need on Python >= 3.6. --- google/api_core/gapic_v1/method.py | 5 +-- google/api_core/gapic_v1/method_async.py | 6 ++-- google/api_core/general_helpers.py | 19 ++--------- google/api_core/grpc_helpers.py | 3 +- google/api_core/retry.py | 3 +- google/api_core/timeout.py | 6 ++-- tests/unit/test_general_helpers.py | 41 ------------------------ 7 files changed, 14 insertions(+), 69 deletions(-) delete mode 100644 tests/unit/test_general_helpers.py diff --git a/google/api_core/gapic_v1/method.py b/google/api_core/gapic_v1/method.py index 8bf82569..cc9ec4ba 100644 --- a/google/api_core/gapic_v1/method.py +++ b/google/api_core/gapic_v1/method.py @@ -18,7 +18,8 @@ pagination, and long-running operations to gRPC methods. """ -from google.api_core import general_helpers +import functools + from google.api_core import grpc_helpers from google.api_core import timeout from google.api_core.gapic_v1 import client_info @@ -237,7 +238,7 @@ def get_topic(name, timeout=None): else: user_agent_metadata = None - return general_helpers.wraps(func)( + return functools.wraps(func)( _GapicCallable( func, default_retry, default_timeout, metadata=user_agent_metadata ) diff --git a/google/api_core/gapic_v1/method_async.py b/google/api_core/gapic_v1/method_async.py index 76e57577..84c99aa2 100644 --- a/google/api_core/gapic_v1/method_async.py +++ b/google/api_core/gapic_v1/method_async.py @@ -17,7 +17,9 @@ pagination, and long-running operations to gRPC methods. """ -from google.api_core import general_helpers, grpc_helpers_async +import functools + +from google.api_core import grpc_helpers_async from google.api_core.gapic_v1 import client_info from google.api_core.gapic_v1.method import _GapicCallable from google.api_core.gapic_v1.method import DEFAULT # noqa: F401 @@ -41,6 +43,6 @@ def wrap_method( metadata = [client_info.to_grpc_metadata()] if client_info is not None else None - return general_helpers.wraps(func)( + return functools.wraps(func)( _GapicCallable(func, default_retry, default_timeout, metadata=metadata) ) diff --git a/google/api_core/general_helpers.py b/google/api_core/general_helpers.py index 33da2381..fba78026 100644 --- a/google/api_core/general_helpers.py +++ b/google/api_core/general_helpers.py @@ -12,20 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Helpers for general Python functionality.""" - -import functools - - -# functools.partial objects lack several attributes present on real function -# objects. In Python 2 wraps fails on this so use a restricted set instead. -_PARTIAL_VALID_ASSIGNMENTS = ("__doc__",) - - -def wraps(wrapped): - """A functools.wraps helper that handles partial objects on Python 2.""" - # https://github.com/google/pytype/issues/322 - if isinstance(wrapped, functools.partial): # pytype: disable=wrong-arg-types - return functools.wraps(wrapped, assigned=_PARTIAL_VALID_ASSIGNMENTS) - else: - return functools.wraps(wrapped) +# This import for backward compatibiltiy only. +from functools import wraps # noqa: F401 pragma: NO COVER diff --git a/google/api_core/grpc_helpers.py b/google/api_core/grpc_helpers.py index a232a6aa..a627c9a6 100644 --- a/google/api_core/grpc_helpers.py +++ b/google/api_core/grpc_helpers.py @@ -21,7 +21,6 @@ import pkg_resources from google.api_core import exceptions -from google.api_core import general_helpers import google.auth import google.auth.credentials import google.auth.transport.grpc @@ -148,7 +147,7 @@ def _wrap_stream_errors(callable_): """ _patch_callable_name(callable_) - @general_helpers.wraps(callable_) + @functools.wraps(callable_) def error_remapped_callable(*args, **kwargs): try: result = callable_(*args, **kwargs) diff --git a/google/api_core/retry.py b/google/api_core/retry.py index 4a5192c3..94ec662a 100644 --- a/google/api_core/retry.py +++ b/google/api_core/retry.py @@ -66,7 +66,6 @@ def check_if_exists(): from google.api_core import datetime_helpers from google.api_core import exceptions -from google.api_core import general_helpers from google.auth import exceptions as auth_exceptions _LOGGER = logging.getLogger(__name__) @@ -270,7 +269,7 @@ def __call__(self, func, on_error=None): if self._on_error is not None: on_error = self._on_error - @general_helpers.wraps(func) + @functools.wraps(func) def retry_wrapped_func(*args, **kwargs): """A wrapper that calls target function with retry.""" target = functools.partial(func, *args, **kwargs) diff --git a/google/api_core/timeout.py b/google/api_core/timeout.py index 79fddd17..73232180 100644 --- a/google/api_core/timeout.py +++ b/google/api_core/timeout.py @@ -54,9 +54,9 @@ def is_thing_ready(timeout=None): from __future__ import unicode_literals import datetime +import functools from google.api_core import datetime_helpers -from google.api_core import general_helpers _DEFAULT_INITIAL_TIMEOUT = 5.0 # seconds _DEFAULT_MAXIMUM_TIMEOUT = 30.0 # seconds @@ -92,7 +92,7 @@ def __call__(self, func): Callable: The wrapped function. """ - @general_helpers.wraps(func) + @functools.wraps(func) def func_with_timeout(*args, **kwargs): """Wrapped function that adds timeout.""" kwargs["timeout"] = self._timeout @@ -203,7 +203,7 @@ def __call__(self, func): self._initial, self._maximum, self._multiplier, self._deadline ) - @general_helpers.wraps(func) + @functools.wraps(func) def func_with_timeout(*args, **kwargs): """Wrapped function that adds timeout.""" kwargs["timeout"] = next(timeouts) diff --git a/tests/unit/test_general_helpers.py b/tests/unit/test_general_helpers.py deleted file mode 100644 index 027d4892..00000000 --- a/tests/unit/test_general_helpers.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2017, Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import functools - -from google.api_core import general_helpers - - -def test_wraps_normal_func(): - def func(): - return 42 - - @general_helpers.wraps(func) - def replacement(): - return func() - - assert replacement() == 42 - - -def test_wraps_partial(): - def func(): - return 42 - - partial = functools.partial(func) - - @general_helpers.wraps(partial) - def replacement(): - return func() - - assert replacement() == 42 From 90c1bd1573a1afe670e902cdd1e7a71229b211c9 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 16 Jun 2021 14:45:54 -0400 Subject: [PATCH 10/22] chore: note URL for follow-up issue --- google/api_core/bidi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/google/api_core/bidi.py b/google/api_core/bidi.py index 44114305..56a021a9 100644 --- a/google/api_core/bidi.py +++ b/google/api_core/bidi.py @@ -644,6 +644,7 @@ def _thread_main(self, ready): # Keeping the lock throughout avoids that. # In the future, we could use `Condition.wait_for` if we drop # Python 2.7. + # See: https://github.com/googleapis/python-api-core/issues/211 with self._wake: while self._paused: _LOGGER.debug("paused, waiting for waking.") From 026ee315725ec9b1c803ccfd0affa46d6deb9e77 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 16 Jun 2021 14:46:18 -0400 Subject: [PATCH 11/22] chore: remove Python 2-specific kwargs hack --- google/api_core/gapic_v1/method.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/google/api_core/gapic_v1/method.py b/google/api_core/gapic_v1/method.py index cc9ec4ba..79722c07 100644 --- a/google/api_core/gapic_v1/method.py +++ b/google/api_core/gapic_v1/method.py @@ -111,26 +111,22 @@ def __init__(self, target, retry, timeout, metadata=None): self._timeout = timeout self._metadata = metadata - def __call__(self, *args, **kwargs): + def __call__(self, *args, timeout=DEFAULT, retry=DEFAULT, **kwargs): """Invoke the low-level RPC with retry, timeout, and metadata.""" - # Note: Due to Python 2 lacking keyword-only arguments we use kwargs to - # extract the retry and timeout params. - timeout_ = _determine_timeout( + timeout = _determine_timeout( self._timeout, - kwargs.pop("timeout", self._timeout), + timeout, # Use only the invocation-specified retry only for this, as we only # want to adjust the timeout deadline if the *user* specified # a different retry. - kwargs.get("retry", None), + retry, ) - retry = kwargs.pop("retry", self._retry) - if retry is DEFAULT: retry = self._retry # Apply all applicable decorators. - wrapped_func = _apply_decorators(self._target, [retry, timeout_]) + wrapped_func = _apply_decorators(self._target, [retry, timeout]) # Add the user agent metadata to the call. if self._metadata is not None: From 4165633259d9df91059093450860144f96f1c04f Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 16 Jun 2021 14:49:33 -0400 Subject: [PATCH 12/22] chore: drop Python2-specific alias --- google/api_core/grpc_helpers.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/google/api_core/grpc_helpers.py b/google/api_core/grpc_helpers.py index a627c9a6..594df987 100644 --- a/google/api_core/grpc_helpers.py +++ b/google/api_core/grpc_helpers.py @@ -92,7 +92,7 @@ def __iter__(self): """This iterator is also an iterable that returns itself.""" return self - def next(self): + def __next__(self): """Get the next response from the stream. Returns: @@ -108,9 +108,6 @@ def next(self): # If the stream has already returned data, we cannot recover here. raise exceptions.from_grpc_error(exc) from exc - # Alias needed for Python 2/3 support. - __next__ = next - # grpc.Call & grpc.RpcContext interface def add_callback(self, callback): From ad7b40bd3636c48cfbc47d14bc49d01f5215464a Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 16 Jun 2021 14:50:25 -0400 Subject: [PATCH 13/22] chore: drop Python2-specific aliases --- google/api_core/page_iterator.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/google/api_core/page_iterator.py b/google/api_core/page_iterator.py index 6fbd91d8..7ddc5cbc 100644 --- a/google/api_core/page_iterator.py +++ b/google/api_core/page_iterator.py @@ -125,7 +125,7 @@ def __iter__(self): """The :class:`Page` is an iterator of items.""" return self - def next(self): + def __next__(self): """Get the next value in the page.""" item = next(self._item_iter) result = self._item_to_value(self._parent, item) @@ -134,9 +134,6 @@ def next(self): self._remaining -= 1 return result - # Alias needed for Python 2/3 support. - __next__ = next - def _item_to_value_identity(iterator, item): """An item to value transformer that returns the item un-changed.""" @@ -232,9 +229,6 @@ def __next__(self): self.__active_iterator = iter(self) return next(self.__active_iterator) - # Preserve Python 2 compatibility. - next = __next__ - def _page_iter(self, increment): """Generator of pages of API responses. From dbc234fd80a29f3a77a3456502fe6ae8fce79eae Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 16 Jun 2021 14:54:56 -0400 Subject: [PATCH 14/22] chore: drop Python2-specific aliases --- tests/unit/test_bidi.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/unit/test_bidi.py b/tests/unit/test_bidi.py index a8436c37..b876d9ad 100644 --- a/tests/unit/test_bidi.py +++ b/tests/unit/test_bidi.py @@ -221,18 +221,12 @@ def cancel_side_effect(): class ClosedCall(object): - # NOTE: This is needed because defining `.next` on an **instance** - # rather than the **class** will not be iterable in Python 2. - # This is problematic since a `Mock` just sets members. - def __init__(self, exception): self.exception = exception def __next__(self): raise self.exception - next = __next__ # Python 2 - def is_active(self): return False @@ -354,8 +348,6 @@ def __next__(self): raise item return item - next = __next__ # Python 2 - def is_active(self): return self._is_active From 72d175b1207f367bcd6f683f1e180d5257117ccc Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 7 Jul 2021 11:44:03 -0400 Subject: [PATCH 15/22] chore: remove 'packaging' dependency Closes #215. --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index d4e9a3fa..736bcc16 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,6 @@ "google-auth >= 1.25.0, < 2.0dev", "requests >= 2.18.0, < 3.0.0dev", "setuptools >= 40.3.0", - "packaging >= 14.3", 'futures >= 3.2.0; python_version < "3.2"', ] extras = { From 0f6a745406bf5012b6ddede56a66638f09408c26 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Wed, 14 Jul 2021 19:40:54 +0000 Subject: [PATCH 16/22] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/master/packages/owl-bot/README.md --- google/api_core/version.py | 1 - 1 file changed, 1 deletion(-) diff --git a/google/api_core/version.py b/google/api_core/version.py index b992976b..c9cdad6f 100644 --- a/google/api_core/version.py +++ b/google/api_core/version.py @@ -13,4 +13,3 @@ # limitations under the License. __version__ = "2.0.0-b1" - From cff328fc51980f77d838e2494835c07164c7f4ae Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 14 Jul 2021 15:48:50 -0400 Subject: [PATCH 17/22] chore: switch to microgenerator to avoid 2.7 reappearing --- owlbot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/owlbot.py b/owlbot.py index 673f6d8c..451f7c48 100644 --- a/owlbot.py +++ b/owlbot.py @@ -29,7 +29,7 @@ ".coveragerc", # layout "CONTRIBUTING.rst", # no systests ] -templated_files = common.py_library(cov_level=100) +templated_files = common.py_library(microgenerator=True, cov_level=100) s.move(templated_files, excludes=excludes) # Add pytype support From 3a66b33be9fb39ddfbc193f01aeec115afa3b18f Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 14 Jul 2021 16:54:55 -0400 Subject: [PATCH 18/22] chore: expand range to allow 'google-auth' 2.x versions --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 736bcc16..d98c69c5 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ dependencies = [ "googleapis-common-protos >= 1.6.0, < 2.0dev", "protobuf >= 3.12.0", - "google-auth >= 1.25.0, < 2.0dev", + "google-auth >= 1.25.0, < 3.0dev", "requests >= 2.18.0, < 3.0.0dev", "setuptools >= 40.3.0", 'futures >= 3.2.0; python_version < "3.2"', From 9c9a9a5d378b5dc3188d5b15d89fb518547b864f Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 27 Jul 2021 12:53:45 -0400 Subject: [PATCH 19/22] docs: sync newest version w/ Python 2.7 support --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index dde28796..ee94d93a 100644 --- a/README.rst +++ b/README.rst @@ -25,4 +25,4 @@ Unsupported Python Versions Python == 2.7, Python == 3.5. The last version of this library compatible with Python 2.7 and 3.5 is -`google-api_core==1.30.0`. +`google-api_core==1.30.1`. From 01d4c8f75a91df6282d2412dbde6e0c4e5e38ba0 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Sun, 1 Aug 2021 15:06:24 -0400 Subject: [PATCH 20/22] chore: drop comment referring to Python 2.7 / pytypes --- noxfile.py | 1 - 1 file changed, 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index 8b5db08c..39791176 100644 --- a/noxfile.py +++ b/noxfile.py @@ -136,7 +136,6 @@ def lint_setup_py(session): session.run("python", "setup.py", "check", "--restructuredtext", "--strict") -# No 2.7 due to https://github.com/google/importlab/issues/26. # No 3.7 because pytype supports up to 3.6 only. @nox.session(python="3.6") def pytype(session): From 895d8953bbf0df1f8d8b59605e180c0806a5461c Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Sun, 1 Aug 2021 15:07:20 -0400 Subject: [PATCH 21/22] chore: remove mentions of Python 2.7 from docs Except for the README section documentin the last version supporting it. --- CONTRIBUTING.rst | 6 ++---- README.rst | 2 +- docs/auth.rst | 19 ------------------- google/api_core/client_info.py | 2 +- google/api_core/gapic_v1/client_info.py | 2 +- .../templates/install_deps.tmpl.rst | 2 +- 6 files changed, 6 insertions(+), 27 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 1ecdd0ae..358404f2 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -21,7 +21,7 @@ In order to add a feature: documentation. - The feature must work fully on the following CPython versions: - 2.7, 3.6, 3.7, 3.8 and 3.9 on both UNIX and Windows. + 3.6, 3.7, 3.8 and 3.9 on both UNIX and Windows. - The feature must not add unnecessary dependencies (where "unnecessary" is of course subjective, but new dependencies should @@ -197,13 +197,11 @@ Supported Python Versions We support: -- `Python 2.7`_ - `Python 3.6`_ - `Python 3.7`_ - `Python 3.8`_ - `Python 3.9`_ -.. _Python 2.7: https://docs.python.org/2.7/ .. _Python 3.6: https://docs.python.org/3.6/ .. _Python 3.7: https://docs.python.org/3.7/ .. _Python 3.8: https://docs.python.org/3.8/ @@ -215,7 +213,7 @@ Supported versions can be found in our ``noxfile.py`` `config`_. .. _config: https://github.com/googleapis/python-api-core/blob/master/noxfile.py -We also explicitly decided to support Python 3 beginning with version 2.7. +We also explicitly decided to support Python 3 beginning with version 3.6. Reasons for this include: - Encouraging use of newest versions of Python 3 diff --git a/README.rst b/README.rst index ee94d93a..d94f3e88 100644 --- a/README.rst +++ b/README.rst @@ -25,4 +25,4 @@ Unsupported Python Versions Python == 2.7, Python == 3.5. The last version of this library compatible with Python 2.7 and 3.5 is -`google-api_core==1.30.1`. +`google-api_core==1.31.1`. diff --git a/docs/auth.rst b/docs/auth.rst index faf0228f..a9b296d4 100644 --- a/docs/auth.rst +++ b/docs/auth.rst @@ -103,25 +103,6 @@ After creation, you can pass it directly to a :class:`Client ` -just for Google App Engine: - -.. code:: python - - from google.auth import app_engine - credentials = app_engine.Credentials() - Google Compute Engine Environment --------------------------------- diff --git a/google/api_core/client_info.py b/google/api_core/client_info.py index adca5f32..d7f4367a 100644 --- a/google/api_core/client_info.py +++ b/google/api_core/client_info.py @@ -42,7 +42,7 @@ class ClientInfo(object): Args: python_version (str): The Python interpreter version, for example, - ``'2.7.13'``. + ``'3.9.6'``. grpc_version (Optional[str]): The gRPC library version. api_core_version (str): The google-api-core library version. gapic_version (Optional[str]): The sversion of gapic-generated client diff --git a/google/api_core/gapic_v1/client_info.py b/google/api_core/gapic_v1/client_info.py index bdc2ce44..fab0f542 100644 --- a/google/api_core/gapic_v1/client_info.py +++ b/google/api_core/gapic_v1/client_info.py @@ -33,7 +33,7 @@ class ClientInfo(client_info.ClientInfo): Args: python_version (str): The Python interpreter version, for example, - ``'2.7.13'``. + ``'3.9.6'``. grpc_version (Optional[str]): The gRPC library version. api_core_version (str): The google-api-core library version. gapic_version (Optional[str]): The sversion of gapic-generated client diff --git a/scripts/readme-gen/templates/install_deps.tmpl.rst b/scripts/readme-gen/templates/install_deps.tmpl.rst index a0406dba..275d6498 100644 --- a/scripts/readme-gen/templates/install_deps.tmpl.rst +++ b/scripts/readme-gen/templates/install_deps.tmpl.rst @@ -12,7 +12,7 @@ Install Dependencies .. _Python Development Environment Setup Guide: https://cloud.google.com/python/setup -#. Create a virtualenv. Samples are compatible with Python 2.7 and 3.4+. +#. Create a virtualenv. Samples are compatible with Python 3.6+. .. code-block:: bash From 556ff21f93e1fd3463e58e8694037398df6c46ff Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Sun, 1 Aug 2021 19:09:13 +0000 Subject: [PATCH 22/22] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/master/packages/owl-bot/README.md --- scripts/readme-gen/templates/install_deps.tmpl.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/readme-gen/templates/install_deps.tmpl.rst b/scripts/readme-gen/templates/install_deps.tmpl.rst index 275d6498..a0406dba 100644 --- a/scripts/readme-gen/templates/install_deps.tmpl.rst +++ b/scripts/readme-gen/templates/install_deps.tmpl.rst @@ -12,7 +12,7 @@ Install Dependencies .. _Python Development Environment Setup Guide: https://cloud.google.com/python/setup -#. Create a virtualenv. Samples are compatible with Python 3.6+. +#. Create a virtualenv. Samples are compatible with Python 2.7 and 3.4+. .. code-block:: bash