Skip to content

fix!: Drop Python 2.7 and pytz #74

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -69,7 +69,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.7
$ ...

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

Expand Down
4 changes: 0 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
16 changes: 7 additions & 9 deletions google/api_core/datetime_helpers.py
Original file line number Diff line number Diff line change
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.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
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,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
Expand All @@ -292,5 +290,5 @@ def from_timestamp_pb(cls, stamp):
bare.minute,
bare.second,
nanosecond=stamp.nanos,
tzinfo=pytz.UTC,
tzinfo=datetime.timezone.utc,
)
5 changes: 2 additions & 3 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand Down Expand Up @@ -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):
Expand Down
7 changes: 3 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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",
],
Expand All @@ -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,
)
47 changes: 24 additions & 23 deletions tests/unit/test_datetime_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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


Expand All @@ -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

Expand All @@ -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)
Expand All @@ -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
)


Expand All @@ -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
)


Expand Down Expand Up @@ -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
)


Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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(
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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):
Expand All @@ -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())