Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix!: drop support for Python 2.7 / 3.5 #212

Merged
merged 28 commits into from Aug 3, 2021
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
21ac69a
fix!: drop support for Python 2.7 and 3.5
tseaver Jun 15, 2021
78caa92
chore: drop 'six' module
tseaver Jun 15, 2021
35c8216
chore: drop 'u"' prefixes for text
tseaver Jun 15, 2021
35ba7d3
chore: remove other Python 2.7 workarounds
tseaver Jun 15, 2021
61490f5
chore: drop use of 'pytz'
tseaver Jun 15, 2021
74c6179
Merge branch 'master' into 210-drop-python-2.7
tseaver Jun 16, 2021
6e21b01
tests: scrub more 2.7 / six constraints
tseaver Jun 16, 2021
fe7d454
docs: add section on unspported Python versions to README
tseaver Jun 16, 2021
d77fbb0
chore: drop workaround for Python 2.7
tseaver Jun 16, 2021
440e571
chore: remove 'general_helpers.wraps'
tseaver Jun 16, 2021
90c1bd1
chore: note URL for follow-up issue
tseaver Jun 16, 2021
026ee31
chore: remove Python 2-specific kwargs hack
tseaver Jun 16, 2021
4165633
chore: drop Python2-specific alias
tseaver Jun 16, 2021
ad7b40b
chore: drop Python2-specific aliases
tseaver Jun 16, 2021
dbc234f
chore: drop Python2-specific aliases
tseaver Jun 16, 2021
4d87012
Merge branch 'master' into 210-drop-python-2.7
tseaver Jun 30, 2021
c480784
Merge branch 'master' into 210-drop-python-2.7
tseaver Jul 7, 2021
72d175b
chore: remove 'packaging' dependency
tseaver Jul 7, 2021
01b49c4
Merge branch 'master' into 210-drop-python-2.7
tseaver Jul 14, 2021
0f6a745
🦉 Updates from OwlBot
gcf-owl-bot[bot] Jul 14, 2021
cff328f
chore: switch to microgenerator to avoid 2.7 reappearing
tseaver Jul 14, 2021
3a66b33
chore: expand range to allow 'google-auth' 2.x versions
tseaver Jul 14, 2021
690a971
Merge branch 'master' into 210-drop-python-2.7
tseaver Jul 23, 2021
dc30a49
Merge branch 'master' into 210-drop-python-2.7
tseaver Jul 27, 2021
9c9a9a5
docs: sync newest version w/ Python 2.7 support
tseaver Jul 27, 2021
01d4c8f
chore: drop comment referring to Python 2.7 / pytypes
tseaver Aug 1, 2021
895d895
chore: remove mentions of Python 2.7 from docs
tseaver Aug 1, 2021
556ff21
🦉 Updates from OwlBot
gcf-owl-bot[bot] Aug 1, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 2 additions & 30 deletions CONTRIBUTING.rst
@@ -1,4 +1,3 @@
.. Generated by synthtool. DO NOT EDIT!
############
Contributing
############
Expand Down Expand Up @@ -69,7 +68,6 @@ We use `nox <https://nox.readthedocs.io/en/latest/>`__ to instrument our tests.

- To test your changes, run unit tests with ``nox``::

$ nox -s unit-2.7
$ nox -s unit-3.8
$ ...

Expand All @@ -80,8 +78,8 @@ We use `nox <https://nox.readthedocs.io/en/latest/>`__ 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/

Expand Down Expand Up @@ -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 <name of test>


.. 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 <https://cloud.google.com/docs/authentication/best-practices-applications#local_development_and_testing_with_the>`__. Some tests require a service account. For those tests see `Authenticating as a service account <https://cloud.google.com/docs/authentication/production>`__.

*************
Test Coverage
*************
Expand Down
15 changes: 10 additions & 5 deletions 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
Expand All @@ -16,8 +16,13 @@ common helpers used by all Google API clients. For more information, see the

Supported Python Versions
-------------------------
Python >= 3.5
Python >= 3.6

Deprecated Python Versions
--------------------------
Python == 2.7. Python 2.7 support will be removed on January 1, 2020.
tseaver marked this conversation as resolved.
Show resolved Hide resolved

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`.
10 changes: 5 additions & 5 deletions google/api_core/bidi.py
Expand Up @@ -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__)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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."
Expand Down Expand Up @@ -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 = []
Expand Down Expand Up @@ -645,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.")
Expand Down
2 changes: 1 addition & 1 deletion google/api_core/client_options.py
Expand Up @@ -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.
"""

Expand Down
20 changes: 11 additions & 9 deletions google/api_core/datetime_helpers.py
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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):
Expand All @@ -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
Expand All @@ -292,5 +294,5 @@ def from_timestamp_pb(cls, stamp):
bare.minute,
bare.second,
nanosecond=stamp.nanos,
tzinfo=pytz.UTC,
tzinfo=datetime.timezone.utc,
)
47 changes: 21 additions & 26 deletions google/api_core/exceptions.py
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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.
"""

Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -266,26 +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.
code = 429
code = http.client.TOO_MANY_REQUESTS


class ResourceExhausted(TooManyRequests):
Expand All @@ -312,7 +307,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


Expand All @@ -332,28 +327,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):
Expand Down