From 475b569377f28d462d26efe86b6484646064a428 Mon Sep 17 00:00:00 2001 From: "Bob \"Wombat\" Hogg" Date: Tue, 6 Dec 2022 15:28:03 -0500 Subject: [PATCH 1/8] build(nox): Use Python 3.9 for docs and doctest sessions (#847) 3.8 was previously used, but it's not available in the currently used Docker image. --- noxfile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/noxfile.py b/noxfile.py index 4fd29a47..00388b6b 100644 --- a/noxfile.py +++ b/noxfile.py @@ -119,7 +119,7 @@ def blacken(session): run_black(session) -@nox.session(py=DEFAULT_INTERPRETER) +@nox.session(py="3.9") def docs(session): """Build the docs for this library.""" @@ -143,7 +143,7 @@ def docs(session): ) -@nox.session(py=DEFAULT_INTERPRETER) +@nox.session(py="3.9") def doctest(session): # Install all dependencies. session.install("Sphinx==4.0.1") From d2a33668f0f3d0eaa9f2220374b62066c8312d10 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 9 Dec 2022 11:37:26 -0500 Subject: [PATCH 2/8] build(deps): bump certifi from 2022.9.24 to 2022.12.7 in /synthtool/gcp/templates/python_library/.kokoro (#849) Source-Link: https://github.com/googleapis/synthtool/commit/b4fe62efb5114b6738ad4b13d6f654f2bf4b7cc0 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:3bf87e47c2173d7eed42714589dc4da2c07c3268610f1e47f8e1a30decbfc7f1 Co-authored-by: Owl Bot --- .github/.OwlBot.lock.yaml | 2 +- .kokoro/requirements.txt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index bb21147e..fccaa8e8 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -13,4 +13,4 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:3abfa0f1886adaf0b83f07cb117b24a639ea1cb9cffe56d43280b977033563eb + digest: sha256:3bf87e47c2173d7eed42714589dc4da2c07c3268610f1e47f8e1a30decbfc7f1 diff --git a/.kokoro/requirements.txt b/.kokoro/requirements.txt index 9c1b9be3..05dc4672 100644 --- a/.kokoro/requirements.txt +++ b/.kokoro/requirements.txt @@ -20,9 +20,9 @@ cachetools==5.2.0 \ --hash=sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757 \ --hash=sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db # via google-auth -certifi==2022.9.24 \ - --hash=sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14 \ - --hash=sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382 +certifi==2022.12.7 \ + --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \ + --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18 # via requests cffi==1.15.1 \ --hash=sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5 \ From c1ab83b9581b3d4d10dc7d2508b1c93b14e3c31a Mon Sep 17 00:00:00 2001 From: "Bob \"Wombat\" Hogg" Date: Mon, 12 Dec 2022 11:51:18 -0500 Subject: [PATCH 3/8] fix(zlib): Accomodate different Zlib compression levels (#852) Different Zlib compression levels produce different compression markers. Co-authored-by: Zhou Wang Co-authored-by: Zhou Wang --- google/cloud/ndb/model.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/google/cloud/ndb/model.py b/google/cloud/ndb/model.py index 340b8d81..21c5137b 100644 --- a/google/cloud/ndb/model.py +++ b/google/cloud/ndb/model.py @@ -334,10 +334,14 @@ class Person(Model): _MEANING_PREDEFINED_ENTITY_USER = 20 _MEANING_COMPRESSED = 22 -# As produced by zlib. Indicates compressed byte sequence using DEFLATE at -# default compression level, with a 32K window size. -# From https://github.com/madler/zlib/blob/master/doc/rfc1950.txt -_ZLIB_COMPRESSION_MARKER = b"x\x9c" +_ZLIB_COMPRESSION_MARKERS = ( + # As produced by zlib. Indicates compressed byte sequence using DEFLATE at + # default compression level, with a 32K window size. + # From https://github.com/madler/zlib/blob/master/doc/rfc1950.txt + b"x\x9c", + # Other compression levels produce the following marker. + b"x^", +) _MAX_STRING_LENGTH = 1500 Key = key_module.Key @@ -2619,7 +2623,7 @@ def _from_base_type(self, value): return if self._compressed and not isinstance(value, _CompressedValue): - if not value.startswith(_ZLIB_COMPRESSION_MARKER): + if not value.startswith(_ZLIB_COMPRESSION_MARKERS): return value value = _CompressedValue(value) @@ -2645,13 +2649,13 @@ def _to_datastore(self, entity, data, prefix="", repeated=False): if self._repeated: compressed_value = [] for rval in value: - if rval and not rval.startswith(_ZLIB_COMPRESSION_MARKER): + if rval and not rval.startswith(_ZLIB_COMPRESSION_MARKERS): rval = zlib.compress(rval) compressed_value.append(rval) value = compressed_value data[key] = value if not self._repeated: - if value and not value.startswith(_ZLIB_COMPRESSION_MARKER): + if value and not value.startswith(_ZLIB_COMPRESSION_MARKERS): value = zlib.compress(value) data[key] = value From 6f94f40dfcd6f10e3cec979e4eb2b83408c66a30 Mon Sep 17 00:00:00 2001 From: "Bob \"Wombat\" Hogg" Date: Tue, 13 Dec 2022 16:37:12 -0500 Subject: [PATCH 4/8] feat: Support client_options for clients (#815) Currently, NDB clients do not recognize client_options as a keyword argument. Furthermore, because they currently create a Datastore channel directly, we have to manually modify the host to ensure it is passed to the Datastore channel. Merely setting the API endpoint via environment variable is not sufficient, because that variable's presence is currently used to test whether or not we are using the datastore emulator. Bump api-core dep to ensure we have the necessary API support, also sync it up with google-cloud-datastore. --- google/cloud/ndb/client.py | 31 ++++++++++++++++++++++++++++--- setup.py | 1 + testing/constraints-3.7.txt | 1 + tests/unit/test_client.py | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 3 deletions(-) diff --git a/google/cloud/ndb/client.py b/google/cloud/ndb/client.py index a9a656c7..2ea7d963 100644 --- a/google/cloud/ndb/client.py +++ b/google/cloud/ndb/client.py @@ -19,6 +19,8 @@ import os import requests +import google.api_core.client_options + from google.api_core.gapic_v1 import client_info from google.cloud import environment_vars from google.cloud import _helpers @@ -76,6 +78,9 @@ class Client(google_client.ClientWithProject): The NDB client must be created in order to use NDB, and any use of NDB must be within the context of a call to :meth:`context`. + The Datastore Emulator is used for the client if and only if the + DATASTORE_EMULATOR_HOST environment variable is set. + Arguments: project (Optional[str]): The project to pass to proxied API methods. If not passed, falls back to the default inferred from the @@ -84,21 +89,38 @@ class Client(google_client.ClientWithProject): credentials (Optional[:class:`~google.auth.credentials.Credentials`]): The OAuth2 Credentials to use for this client. If not passed, falls back to the default inferred from the environment. + client_options (Optional[:class:`~google.api_core.client_options.ClientOptions` or :class:`dict`]) + Client options used to set user options on the client. + API Endpoint should be set through client_options. """ SCOPE = ("https://www.googleapis.com/auth/datastore",) """The scopes required for authenticating as a Cloud Datastore consumer.""" - def __init__(self, project=None, namespace=None, credentials=None): + def __init__( + self, project=None, namespace=None, credentials=None, client_options=None + ): self.namespace = namespace - self.host = os.environ.get(environment_vars.GCD_HOST, DATASTORE_API_HOST) self.client_info = _CLIENT_INFO + self._client_options = client_options # Use insecure connection when using Datastore Emulator, otherwise # use secure connection emulator = bool(os.environ.get(environment_vars.GCD_HOST)) self.secure = not emulator + # Use Datastore API host from client_options if provided, otherwise use default + api_endpoint = DATASTORE_API_HOST + if client_options is not None: + if type(client_options) == dict: + client_options = google.api_core.client_options.from_dict( + client_options + ) + if client_options.api_endpoint: + api_endpoint = client_options.api_endpoint + + self.host = os.environ.get(environment_vars.GCD_HOST, api_endpoint) + if emulator: # When using the emulator, in theory, the client shouldn't need to # call home to authenticate, as you don't need to authenticate to @@ -108,10 +130,13 @@ def __init__(self, project=None, namespace=None, credentials=None): super(Client, self).__init__( project=project, credentials=credentials, + client_options=client_options, _http=requests.Session, ) else: - super(Client, self).__init__(project=project, credentials=credentials) + super(Client, self).__init__( + project=project, credentials=credentials, client_options=client_options + ) if emulator: channel = grpc.insecure_channel(self.host) diff --git a/setup.py b/setup.py index 2c02e60f..77763ae4 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ def main(): with io.open(readme_filename, encoding="utf-8") as readme_file: readme = readme_file.read() dependencies = [ + "google-api-core[grpc] >= 1.34.0, <3.0.0dev,!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,!=2.10.*", "google-cloud-datastore >= 2.7.2, <3.0.0dev", "protobuf >= 3.19.5, <5.0.0dev,!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5", "pymemcache >= 2.1.0, < 5.0.0dev", diff --git a/testing/constraints-3.7.txt b/testing/constraints-3.7.txt index 91a3c0bb..70f746f0 100644 --- a/testing/constraints-3.7.txt +++ b/testing/constraints-3.7.txt @@ -6,6 +6,7 @@ # e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev", # Then this file should have foo==1.14.0 google-cloud-datastore==2.7.2 +google-api-core==1.34.0 protobuf==3.19.5 pymemcache==2.1.0 redis==3.0.0 diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index e5784426..dc7603a0 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -21,6 +21,7 @@ import mock from google.auth import credentials +from google.api_core.client_options import ClientOptions from google.cloud import environment_vars from google.cloud.datastore import _http @@ -81,9 +82,42 @@ def test_constructor_all_args(): project="test-project", namespace="test-namespace", credentials=creds, + client_options=ClientOptions( + api_endpoint="alternate-endpoint.example.com" + ), ) assert client.namespace == "test-namespace" assert client.project == "test-project" + assert client.host == "alternate-endpoint.example.com" + assert client.secure is True + + @staticmethod + def test_constructor_client_options_as_dict(): + with patch_credentials("testing") as creds: + client = client_module.Client( + project="test-project", + namespace="test-namespace", + credentials=creds, + client_options={"api_endpoint": "alternate-endpoint.example.com"}, + ) + assert client.namespace == "test-namespace" + assert client.project == "test-project" + assert client.host == "alternate-endpoint.example.com" + assert client.secure is True + + @staticmethod + def test_constructor_client_options_no_api_endpoint(): + with patch_credentials("testing") as creds: + client = client_module.Client( + project="test-project", + namespace="test-namespace", + credentials=creds, + client_options={"scopes": ["my_scope"]}, + ) + assert client.namespace == "test-namespace" + assert client.project == "test-project" + assert client.host == _http.DATASTORE_API_HOST + assert client.secure is True @staticmethod def test__determine_default(): From d7237b75aa4b781bbb4870f81d6ea8319cd0c98c Mon Sep 17 00:00:00 2001 From: "Bob \"Wombat\" Hogg" Date: Tue, 13 Dec 2022 16:51:25 -0500 Subject: [PATCH 5/8] chore(tests): Remove mock dependency and import (#851) This is available in unittest for Py3. We only needed to do this for Python 2, which is no longer supported. Co-authored-by: Anthonios Partheniou --- noxfile.py | 2 -- tests/conftest.py | 6 +----- tests/system/test_crud.py | 5 +---- tests/system/test_misc.py | 5 +---- tests/unit/test__cache.py | 5 +---- tests/unit/test__datastore_api.py | 5 +---- tests/unit/test__datastore_query.py | 5 +---- tests/unit/test__datastore_types.py | 5 +---- tests/unit/test__eventloop.py | 5 +---- tests/unit/test__remote.py | 5 +---- tests/unit/test__retry.py | 5 +---- tests/unit/test__transaction.py | 5 +---- tests/unit/test_client.py | 5 +---- tests/unit/test_context.py | 5 +---- tests/unit/test_global_cache.py | 5 +---- tests/unit/test_key.py | 5 +---- tests/unit/test_metadata.py | 5 +---- tests/unit/test_model.py | 5 +---- tests/unit/test_polymodel.py | 5 +---- tests/unit/test_query.py | 5 +---- tests/unit/test_tasklets.py | 5 +---- tests/unit/test_utils.py | 5 +---- 22 files changed, 21 insertions(+), 87 deletions(-) diff --git a/noxfile.py b/noxfile.py index 00388b6b..33523676 100644 --- a/noxfile.py +++ b/noxfile.py @@ -45,7 +45,6 @@ def unit(session): ) # Install all dependencies. session.install("pytest", "pytest-cov") - session.install("mock") session.install("google-cloud-testutils", "-c", constraints_path) session.install("-e", ".", "-c", constraints_path) # This variable is used to skip coverage by Python version @@ -190,7 +189,6 @@ def system(session): # Install all test dependencies, then install this package into the # virtualenv's dist-packages. session.install("pytest") - session.install("mock") session.install("google-cloud-testutils") for local_dep in LOCAL_DEPS: session.install(local_dep) diff --git a/tests/conftest.py b/tests/conftest.py index 7c8f0a16..3ed9baf6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -29,11 +29,7 @@ import pytest -# In Python 2.7, mock is not part of unittest -try: - from unittest import mock -except ImportError: - import mock +from unittest import mock utils.DEBUG = True diff --git a/tests/system/test_crud.py b/tests/system/test_crud.py index a2208ff9..00342895 100644 --- a/tests/system/test_crud.py +++ b/tests/system/test_crud.py @@ -23,10 +23,7 @@ import threading import zlib -try: - from unittest import mock -except ImportError: - import mock +from unittest import mock import pytest diff --git a/tests/system/test_misc.py b/tests/system/test_misc.py index d0eb89db..d5bd42ae 100644 --- a/tests/system/test_misc.py +++ b/tests/system/test_misc.py @@ -23,10 +23,7 @@ import redis -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock import pytest diff --git a/tests/unit/test__cache.py b/tests/unit/test__cache.py index 20b7a714..b812c95b 100644 --- a/tests/unit/test__cache.py +++ b/tests/unit/test__cache.py @@ -14,10 +14,7 @@ import warnings -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock import pytest diff --git a/tests/unit/test__datastore_api.py b/tests/unit/test__datastore_api.py index 3700b396..70739f51 100644 --- a/tests/unit/test__datastore_api.py +++ b/tests/unit/test__datastore_api.py @@ -12,10 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock import grpc import pytest diff --git a/tests/unit/test__datastore_query.py b/tests/unit/test__datastore_query.py index ce253ccd..dc93ff7b 100644 --- a/tests/unit/test__datastore_query.py +++ b/tests/unit/test__datastore_query.py @@ -14,10 +14,7 @@ import base64 -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock import pytest diff --git a/tests/unit/test__datastore_types.py b/tests/unit/test__datastore_types.py index 9ad36ec6..f24b677a 100644 --- a/tests/unit/test__datastore_types.py +++ b/tests/unit/test__datastore_types.py @@ -12,10 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock import pytest diff --git a/tests/unit/test__eventloop.py b/tests/unit/test__eventloop.py index 131f5cec..26620088 100644 --- a/tests/unit/test__eventloop.py +++ b/tests/unit/test__eventloop.py @@ -14,10 +14,7 @@ import collections -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock import grpc import pytest diff --git a/tests/unit/test__remote.py b/tests/unit/test__remote.py index 418919a1..0c0bf19e 100644 --- a/tests/unit/test__remote.py +++ b/tests/unit/test__remote.py @@ -12,10 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock import grpc import pytest diff --git a/tests/unit/test__retry.py b/tests/unit/test__retry.py index f77955e9..3cb9e1b9 100644 --- a/tests/unit/test__retry.py +++ b/tests/unit/test__retry.py @@ -14,10 +14,7 @@ import itertools -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock import pytest diff --git a/tests/unit/test__transaction.py b/tests/unit/test__transaction.py index 435b9840..c18590ed 100644 --- a/tests/unit/test__transaction.py +++ b/tests/unit/test__transaction.py @@ -15,10 +15,7 @@ import itertools import logging -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock import pytest diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index dc7603a0..302c1aa6 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -15,10 +15,7 @@ import contextlib import pytest -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock from google.auth import credentials from google.api_core.client_options import ClientOptions diff --git a/tests/unit/test_context.py b/tests/unit/test_context.py index c5441b1a..151b1a52 100644 --- a/tests/unit/test_context.py +++ b/tests/unit/test_context.py @@ -15,10 +15,7 @@ import pytest import threading -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock from google.cloud.ndb import context as context_module from google.cloud.ndb import _eventloop diff --git a/tests/unit/test_global_cache.py b/tests/unit/test_global_cache.py index d2a7b560..c7c73962 100644 --- a/tests/unit/test_global_cache.py +++ b/tests/unit/test_global_cache.py @@ -14,10 +14,7 @@ import collections -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock import pytest import redis as redis_module diff --git a/tests/unit/test_key.py b/tests/unit/test_key.py index 217493b3..ab70bb38 100644 --- a/tests/unit/test_key.py +++ b/tests/unit/test_key.py @@ -15,10 +15,7 @@ import base64 import pickle -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock from google.cloud.datastore import _app_engine_key_pb2 import google.cloud.datastore diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index 8af979de..a3aa5c85 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -12,10 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock import pytest diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py index 508accc0..f95095ef 100644 --- a/tests/unit/test_model.py +++ b/tests/unit/test_model.py @@ -19,10 +19,7 @@ import types import zlib -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock from google.cloud import datastore from google.cloud.datastore import entity as entity_module diff --git a/tests/unit/test_polymodel.py b/tests/unit/test_polymodel.py index 832c5564..d217279b 100644 --- a/tests/unit/test_polymodel.py +++ b/tests/unit/test_polymodel.py @@ -12,10 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock import pytest diff --git a/tests/unit/test_query.py b/tests/unit/test_query.py index 672bce7a..3739cfbf 100644 --- a/tests/unit/test_query.py +++ b/tests/unit/test_query.py @@ -14,10 +14,7 @@ import pickle -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock import pytest import six diff --git a/tests/unit/test_tasklets.py b/tests/unit/test_tasklets.py index ce00f7f1..b88c1af2 100644 --- a/tests/unit/test_tasklets.py +++ b/tests/unit/test_tasklets.py @@ -12,10 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock import pytest diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 0062270e..d22ebc57 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -14,10 +14,7 @@ import threading -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock import pytest From bcf61437426900997d8f33338f5c4d6bf5d0d2d7 Mon Sep 17 00:00:00 2001 From: "Bob \"Wombat\" Hogg" Date: Wed, 14 Dec 2022 16:34:24 -0500 Subject: [PATCH 6/8] test: Remove Py2-only test code (#854) --- tests/unit/test__gql.py | 3 --- tests/unit/test_model.py | 21 +++------------------ tests/unit/test_query.py | 9 --------- 3 files changed, 3 insertions(+), 30 deletions(-) diff --git a/tests/unit/test__gql.py b/tests/unit/test__gql.py index 57898cd7..a8caa069 100644 --- a/tests/unit/test__gql.py +++ b/tests/unit/test__gql.py @@ -14,7 +14,6 @@ import datetime import pytest -import six from google.cloud.ndb import exceptions from google.cloud.ndb import key @@ -288,8 +287,6 @@ class SomeKind(model.Model): gql = gql_module.GQL(GQL_QUERY) query = gql.get_query() compat_rep = "'xxx'" - if six.PY2: # pragma: NO PY3 COVER # pragma: NO BRANCH - compat_rep = "u'xxx'" assert repr(query) == rep.format(compat_rep) @staticmethod diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py index f95095ef..1c9a28bf 100644 --- a/tests/unit/test_model.py +++ b/tests/unit/test_model.py @@ -15,7 +15,6 @@ import datetime import pickle import pytz -import six import types import zlib @@ -971,12 +970,6 @@ def test__find_methods(self): methods = SomeProperty._find_methods("IN", "find_me") expected = [SomeProperty.IN, SomeProperty.find_me, model.Property.IN] - if six.PY2: # pragma: NO PY3 COVER # pragma: NO BRANCH - expected = [ - SomeProperty.IN.__func__, - SomeProperty.find_me.__func__, - model.Property.IN.__func__, - ] assert methods == expected # Check cache key = "{}.{}".format(SomeProperty.__module__, SomeProperty.__name__) @@ -989,12 +982,6 @@ def test__find_methods_reverse(self): methods = SomeProperty._find_methods("IN", "find_me", reverse=True) expected = [model.Property.IN, SomeProperty.find_me, SomeProperty.IN] - if six.PY2: # pragma: NO PY3 COVER # pragma: NO BRANCH - expected = [ - model.Property.IN.__func__, - SomeProperty.find_me.__func__, - SomeProperty.IN.__func__, - ] assert methods == expected # Check cache key = "{}.{}".format(SomeProperty.__module__, SomeProperty.__name__) @@ -2340,9 +2327,8 @@ def test___lt__(self): assert not user_value1 < user_value1 assert user_value1 < user_value2 assert user_value1 < user_value3 - if six.PY3: # pragma: NO PY2 COVER # pragma: NO BRANCH - with pytest.raises(TypeError): - user_value1 < user_value4 + with pytest.raises(TypeError): + user_value1 < user_value4 @staticmethod def test__from_ds_entity(): @@ -5990,8 +5976,7 @@ def test_str_bytestr_meaning(): assert prop._legacy_db_get_value(v, p) == b"foo" @staticmethod - @pytest.mark.skipif(six.PY2, reason="Test for Python 3 only.") - def test_str_utf8(): # pragma: NO PY2 COVER + def test_str_utf8(): prop = model.Property() p = _legacy_entity_pb.Property() v = _legacy_entity_pb.PropertyValue() diff --git a/tests/unit/test_query.py b/tests/unit/test_query.py index 3739cfbf..3ae243a3 100644 --- a/tests/unit/test_query.py +++ b/tests/unit/test_query.py @@ -17,7 +17,6 @@ from unittest import mock import pytest -import six from google.cloud.datastore import entity as datastore_entity from google.cloud.datastore import helpers @@ -2303,8 +2302,6 @@ class SomeKind(model.Model): ) query = query_module.gql(gql_query) compat_rep = "'xxx'" - if six.PY2: # pragma: NO PY3 COVER # pragma: NO BRANCH - compat_rep = "u'xxx'" assert query.__repr__() == rep.format(compat_rep) @staticmethod @@ -2329,8 +2326,6 @@ class SomeKind(model.Model): positional = [5, "xxx"] query = query_module.gql(gql_query, *positional) compat_rep = "'xxx'" - if six.PY2: # pragma: NO PY3 COVER # pragma: NO BRANCH - compat_rep = "u'xxx'" assert query.__repr__() == rep.format(compat_rep) @staticmethod @@ -2355,8 +2350,6 @@ class SomeKind(model.Model): keywords = {"param1": 5, "param2": "xxx"} query = query_module.gql(gql_query, **keywords) compat_rep = "'xxx'" - if six.PY2: # pragma: NO PY3 COVER # pragma: NO BRANCH - compat_rep = "u'xxx'" assert query.__repr__() == rep.format(compat_rep) @staticmethod @@ -2382,6 +2375,4 @@ class SomeKind(model.Model): keywords = {"param1": "xxx"} query = query_module.gql(gql_query, *positional, **keywords) compat_rep = "'xxx'" - if six.PY2: # pragma: NO PY3 COVER # pragma: NO BRANCH - compat_rep = "u'xxx'" assert query.__repr__() == rep.format(compat_rep) From 15fcea2b530ea221748d8e112f1818cfa3ebe4b8 Mon Sep 17 00:00:00 2001 From: "Bob \"Wombat\" Hogg" Date: Thu, 15 Dec 2022 12:11:04 -0500 Subject: [PATCH 7/8] chore: Restore code coverage (#855) Mostly get rid of older Py2-only code --- .coveragerc | 1 - google/cloud/ndb/_eventloop.py | 6 +-- google/cloud/ndb/_legacy_protocol_buffer.py | 16 ++----- google/cloud/ndb/context.py | 52 ++++++++------------- google/cloud/ndb/key.py | 2 +- google/cloud/ndb/model.py | 16 +++---- google/cloud/ndb/tasklets.py | 15 +----- google/cloud/ndb/utils.py | 5 +- noxfile.py | 3 +- tests/unit/test__legacy_entity_pb.py | 5 +- 10 files changed, 36 insertions(+), 85 deletions(-) diff --git a/.coveragerc b/.coveragerc index 735126d5..40f596d9 100644 --- a/.coveragerc +++ b/.coveragerc @@ -7,7 +7,6 @@ show_missing = True exclude_lines = # Re-enable the standard pragma pragma: NO COVER - pragma: NO PY${PY_VERSION} COVER omit = */gapic/*.py */proto/*.py diff --git a/google/cloud/ndb/_eventloop.py b/google/cloud/ndb/_eventloop.py index 4a4a6827..9bfcb82f 100644 --- a/google/cloud/ndb/_eventloop.py +++ b/google/cloud/ndb/_eventloop.py @@ -21,11 +21,7 @@ import uuid import time -# Python 2.7 module name change -try: - import queue -except ImportError: # pragma: NO PY3 COVER - import Queue as queue +import queue from google.cloud.ndb import utils diff --git a/google/cloud/ndb/_legacy_protocol_buffer.py b/google/cloud/ndb/_legacy_protocol_buffer.py index 56d11d73..2ac2ef70 100644 --- a/google/cloud/ndb/_legacy_protocol_buffer.py +++ b/google/cloud/ndb/_legacy_protocol_buffer.py @@ -18,10 +18,7 @@ # Python 3 doesn't have "long" anymore -try: - long(42) -except NameError: # pragma: NO PY2 COVER - long = int +long = int class ProtocolBufferDecodeError(Exception): @@ -31,10 +28,7 @@ class ProtocolBufferDecodeError(Exception): class ProtocolMessage: def MergePartialFromString(self, s): a = array.array("B") - try: - a.frombytes(s) - except AttributeError: # pragma: NO PY3 COVER - a.fromstring(s) + a.frombytes(s) d = Decoder(a, 0, len(a)) self.TryMerge(d) @@ -204,11 +198,7 @@ def getPrefixedString(self): raise ProtocolBufferDecodeError("truncated") r = self.buf[self.idx : self.idx + length] # noqa: E203 self.idx += length - try: - prefixed = r.tobytes() - except AttributeError: # pragma: NO PY3 COVER - prefixed = r.tostring() - return prefixed + return r.tobytes() __all__ = [ diff --git a/google/cloud/ndb/context.py b/google/cloud/ndb/context.py index 8be08662..ff347660 100644 --- a/google/cloud/ndb/context.py +++ b/google/cloud/ndb/context.py @@ -17,6 +17,7 @@ import collections import contextlib +import contextvars import itertools import os import six @@ -60,43 +61,30 @@ def __next__(self): _context_ids = _ContextIds() -try: # pragma: NO PY2 COVER - import contextvars +class _LocalState: + """Thread local state.""" - class _LocalState: - """Thread local state.""" - - def __init__(self): - self._toplevel_context = contextvars.ContextVar( - "_toplevel_context", default=None - ) - self._context = contextvars.ContextVar("_context", default=None) - - @property - def context(self): - return self._context.get() - - @context.setter - def context(self, value): - self._context.set(value) - - @property - def toplevel_context(self): - return self._toplevel_context.get() - - @toplevel_context.setter - def toplevel_context(self, value): - self._toplevel_context.set(value) + def __init__(self): + self._toplevel_context = contextvars.ContextVar( + "_toplevel_context", default=None + ) + self._context = contextvars.ContextVar("_context", default=None) + @property + def context(self): + return self._context.get() -except ImportError: # pragma: NO PY3 COVER + @context.setter + def context(self, value): + self._context.set(value) - class _LocalState(threading.local): - """Thread local state.""" + @property + def toplevel_context(self): + return self._toplevel_context.get() - def __init__(self): - self.context = None - self.toplevel_context = None + @toplevel_context.setter + def toplevel_context(self, value): + self._toplevel_context.set(value) _state = _LocalState() diff --git a/google/cloud/ndb/key.py b/google/cloud/ndb/key.py index 32780c87..054cc1ba 100644 --- a/google/cloud/ndb/key.py +++ b/google/cloud/ndb/key.py @@ -465,7 +465,7 @@ def __getnewargs__(self): state to pickle. The dictionary has three keys ``pairs``, ``app`` and ``namespace``. """ - return ( # pragma: NO PY2 COVER + return ( { "pairs": self.pairs(), "app": self.app(), diff --git a/google/cloud/ndb/model.py b/google/cloud/ndb/model.py index 21c5137b..6b4382c0 100644 --- a/google/cloud/ndb/model.py +++ b/google/cloud/ndb/model.py @@ -349,11 +349,7 @@ class Person(Model): GeoPt = helpers.GeoPoint Rollback = exceptions.Rollback - -try: - _getfullargspec = inspect.getfullargspec -except AttributeError: # pragma: NO PY3 COVER - _getfullargspec = inspect.getargspec +_getfullargspec = inspect.getfullargspec class KindError(exceptions.BadValueError): @@ -3064,9 +3060,9 @@ def _from_base_type(self, value): Returns: Any: The unpickled ``value``. """ - if six.PY3 and type(value) is bytes: # pragma: NO BRANCH - return pickle.loads(value, encoding="bytes") # pragma: NO PY2 COVER - return pickle.loads(value) # pragma: NO PY3 COVER + if type(value) is bytes: # pragma: NO BRANCH + return pickle.loads(value, encoding="bytes") + return pickle.loads(value) # pragma: NO COVER class JsonProperty(BlobProperty): @@ -3313,7 +3309,7 @@ def __eq__(self, other): return self._email == other._email and self._auth_domain == other._auth_domain def __lt__(self, other): - if not isinstance(other, User): # pragma: NO PY2 COVER + if not isinstance(other, User): return NotImplemented return (self._email, self._auth_domain) < ( @@ -4907,7 +4903,7 @@ def __init__(_self, **kwargs): def _get_property_for(self, p, indexed=True, depth=0): """Internal helper to get the Property for a protobuf-level property.""" - if isinstance(p.name(), six.text_type): # pragma: NO PY2 COVER + if isinstance(p.name(), six.text_type): p.set_name(bytes(p.name(), encoding="utf-8")) parts = p.name().decode().split(".") if len(parts) <= depth: diff --git a/google/cloud/ndb/tasklets.py b/google/cloud/ndb/tasklets.py index 2f8e5a55..960c48d3 100644 --- a/google/cloud/ndb/tasklets.py +++ b/google/cloud/ndb/tasklets.py @@ -237,14 +237,7 @@ def get_traceback(self): Union[types.TracebackType, None]: The traceback, or None. """ if self._exception: - try: - traceback = self._exception.__traceback__ - except AttributeError: # pragma: NO PY3 COVER # pragma: NO BRANCH - # Python 2 does not have the helpful traceback attribute, and - # since the exception is not being handled, it appears that - # sys.exec_info can't give us the traceback either. - traceback = None - return traceback + return self._exception.__traceback__ def add_done_callback(self, callback): """Add a callback function to be run upon task completion. Will run @@ -322,11 +315,7 @@ def _advance_tasklet(self, send_value=None, error=None): with self.context.use(): # Send the next value or exception into the generator if error: - try: - traceback = error.__traceback__ - except AttributeError: # pragma: NO PY3 COVER # pragma: NO BRANCH # noqa: E501 - traceback = None - + traceback = error.__traceback__ yielded = self.generator.throw(type(error), error, traceback) else: diff --git a/google/cloud/ndb/utils.py b/google/cloud/ndb/utils.py index 6b4c1535..39ceb4e0 100644 --- a/google/cloud/ndb/utils.py +++ b/google/cloud/ndb/utils.py @@ -20,10 +20,7 @@ import os import threading -try: - _getfullargspec = inspect.getfullargspec -except AttributeError: # pragma: NO PY3 COVER - _getfullargspec = inspect.getargspec +_getfullargspec = inspect.getfullargspec TRUTHY_STRINGS = {"t", "true", "y", "yes", "on", "1"} diff --git a/noxfile.py b/noxfile.py index 33523676..39884563 100644 --- a/noxfile.py +++ b/noxfile.py @@ -76,8 +76,7 @@ def cover(session): # Install all dependencies. session.install("coverage") # Run coverage report. - # TODO return to 100% coverage - session.run("coverage", "report", "--fail-under=99", "--show-missing") + session.run("coverage", "report", "--fail-under=100", "--show-missing") # Erase cached coverage data. session.run("coverage", "erase") diff --git a/tests/unit/test__legacy_entity_pb.py b/tests/unit/test__legacy_entity_pb.py index 332db792..da690a6c 100644 --- a/tests/unit/test__legacy_entity_pb.py +++ b/tests/unit/test__legacy_entity_pb.py @@ -21,10 +21,7 @@ def _get_decoder(s): a = array.array("B") - try: - a.frombytes(s) - except AttributeError: # pragma: NO PY3 COVER - a.fromstring(s) + a.frombytes(s) d = pb_module.Decoder(a, 0, len(a)) return d From be54f0c1c99eefb1cd1423fb249c84c0f615b3e3 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 15 Dec 2022 12:53:05 -0500 Subject: [PATCH 8/8] chore(main): release 2.1.0 (#853) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- CHANGELOG.md | 12 ++++++++++++ setup.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e681c257..54ebb840 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-cloud-ndb/#history +## [2.1.0](https://github.com/googleapis/python-ndb/compare/v2.0.0...v2.1.0) (2022-12-15) + + +### Features + +* Support client_options for clients ([#815](https://github.com/googleapis/python-ndb/issues/815)) ([6f94f40](https://github.com/googleapis/python-ndb/commit/6f94f40dfcd6f10e3cec979e4eb2b83408c66a30)) + + +### Bug Fixes + +* **zlib:** Accomodate different Zlib compression levels ([#852](https://github.com/googleapis/python-ndb/issues/852)) ([c1ab83b](https://github.com/googleapis/python-ndb/commit/c1ab83b9581b3d4d10dc7d2508b1c93b14e3c31a)) + ## [2.0.0](https://github.com/googleapis/python-ndb/compare/v1.12.0...v2.0.0) (2022-12-06) diff --git a/setup.py b/setup.py index 77763ae4..997b921e 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ def main(): setuptools.setup( name="google-cloud-ndb", - version = "2.0.0", + version = "2.1.0", description="NDB library for Google Cloud Datastore", long_description=readme, long_description_content_type="text/markdown",