diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 19a83545..742f54e7 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -21,7 +21,7 @@ In order to add a feature: - The feature must be documented in both the API and narrative documentation. -- The feature must work fully on the following CPython versions: 2.7, +- The feature must work fully on the following CPython versions: 3.5, 3.6, 3.7 and 3.8 on both UNIX and Windows. - The feature must not add unnecessary dependencies (where @@ -69,7 +69,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.7 $ ... @@ -143,13 +142,11 @@ Running System Tests - To run system tests, you can execute:: $ nox -s system-3.7 - $ nox -s system-2.7 .. note:: - System tests are only configured to run under Python 2.7 and - Python 3.7. For expediency, we do not run them in older versions - of Python 3. + System tests are only configured to run under Python 3.7. For expediency, + we do not run them in older versions. This alone will not run the tests. You'll need to change some local auth settings and change some configuration in your project to @@ -226,8 +223,6 @@ Supported versions can be found in our ``noxfile.py`` `config`_. .. _config: https://github.com/googleapis/python-api-core/blob/master/noxfile.py -Python 2.7 support is deprecated. All code changes should maintain Python 2.7 compatibility until January 1, 2020. - We also explicitly decided to support Python 3 beginning with version 3.5. Reasons for this include: diff --git a/README.rst b/README.rst index 244043ea..bfca572e 100644 --- a/README.rst +++ b/README.rst @@ -17,7 +17,3 @@ 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. diff --git a/google/api_core/datetime_helpers.py b/google/api_core/datetime_helpers.py index e52fb1dd..d2c351d6 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.fromtimestamp(0, 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,7 @@ 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 +290,5 @@ def from_timestamp_pb(cls, stamp): bare.minute, bare.second, nanosecond=stamp.nanos, - tzinfo=pytz.UTC, + tzinfo=datetime.timezone.utc, ) diff --git a/noxfile.py b/noxfile.py index 989bb9be..77b34473 100644 --- a/noxfile.py +++ b/noxfile.py @@ -72,13 +72,13 @@ def default(session): session.run(*pytest_args) -@nox.session(python=["2.7", "3.5", "3.6", "3.7", "3.8"]) +@nox.session(python=["3.5", "3.6", "3.7", "3.8"]) def unit(session): """Run the unit test suite.""" default(session) -@nox.session(python=["2.7", "3.5", "3.6", "3.7", "3.8"]) +@nox.session(python=["3.5", "3.6", "3.7", "3.8"]) def unit_grpc_gcp(session): """Run the unit test suite with grpcio-gcp installed.""" @@ -108,7 +108,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): diff --git a/setup.py b/setup.py index 6fa56b3c..d0a8f94a 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,6 @@ "requests >= 2.18.0, < 3.0.0dev", "setuptools >= 34.0.0", "six >= 1.10.0", - "pytz", 'futures >= 3.2.0; python_version < "3.2"', ] extras = { @@ -79,12 +78,12 @@ "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", + "Programming Language :: Python :: 3.9", "Operating System :: OS Independent", "Topic :: Internet", ], @@ -93,7 +92,7 @@ namespace_packages=namespaces, install_requires=dependencies, extras_require=extras, - python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*", + python_requires=">=3.5", include_package_data=True, zip_safe=False, ) diff --git a/tests/unit/test_datetime_helpers.py b/tests/unit/test_datetime_helpers.py index 4ddcf361..53b66841 100644 --- a/tests/unit/test_datetime_helpers.py +++ b/tests/unit/test_datetime_helpers.py @@ -14,14 +14,15 @@ import calendar import datetime +from datetime import timezone, timedelta import pytest -import pytz from google.api_core import datetime_helpers from google.protobuf import timestamp_pb2 +UTC = timezone.utc ONE_MINUTE_IN_MICROSECONDS = 60 * 1e6 @@ -31,7 +32,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=UTC) assert datetime_helpers.to_milliseconds(dt) == 1000 @@ -42,7 +43,7 @@ def test_to_microseconds(): def test_to_microseconds_non_utc(): - zone = pytz.FixedOffset(-1) + zone = timezone(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 +57,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=UTC ) result = datetime_helpers.from_microseconds(five_mins_from_epoch_in_microseconds) @@ -78,28 +79,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, 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, 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, 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, UTC ) @@ -119,7 +120,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, UTC ) @@ -148,7 +149,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, UTC ) @@ -171,20 +172,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=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 = timezone(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 = timezone(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 +284,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=UTC ) stamp = datetime_helpers.DatetimeWithNanoseconds.from_rfc3339(timestamp) assert stamp == expected @@ -292,7 +293,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=UTC ) stamp = datetime_helpers.DatetimeWithNanoseconds.from_rfc3339(timestamp) assert stamp == expected @@ -301,7 +302,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=UTC ) stamp = datetime_helpers.DatetimeWithNanoseconds.from_rfc3339(timestamp) assert stamp == expected @@ -332,7 +333,7 @@ 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=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=UTC ) delta = stamp - datetime_helpers._UTC_EPOCH timestamp = timestamp_pb2.Timestamp( @@ -351,7 +352,7 @@ 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=UTC) delta = when - datetime_helpers._UTC_EPOCH seconds = int(delta.total_seconds()) timestamp = timestamp_pb2.Timestamp(seconds=seconds) @@ -361,11 +362,11 @@ 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 == 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=UTC) delta = when - datetime_helpers._UTC_EPOCH seconds = int(delta.total_seconds()) timestamp = timestamp_pb2.Timestamp(seconds=seconds, nanos=123456789) @@ -375,7 +376,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 == UTC def _to_seconds(value): @@ -387,5 +388,5 @@ def _to_seconds(value): Returns: int: Microseconds since the unix epoch. """ - assert value.tzinfo is pytz.UTC + assert value.tzinfo is UTC return calendar.timegm(value.timetuple())