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/.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 \ 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/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/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/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 340b8d81..6b4382c0 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 @@ -345,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): @@ -2619,7 +2619,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 +2645,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 @@ -3060,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): @@ -3309,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) < ( @@ -4903,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 4fd29a47..39884563 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 @@ -77,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") @@ -119,7 +117,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 +141,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") @@ -190,7 +188,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/setup.py b/setup.py index 2c02e60f..997b921e 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", @@ -33,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", 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/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__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__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 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 e5784426..302c1aa6 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -15,12 +15,10 @@ 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 from google.cloud import environment_vars from google.cloud.datastore import _http @@ -81,9 +79,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(): 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..1c9a28bf 100644 --- a/tests/unit/test_model.py +++ b/tests/unit/test_model.py @@ -15,14 +15,10 @@ import datetime import pickle import pytz -import six 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 @@ -974,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__) @@ -992,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__) @@ -2343,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(): @@ -5993,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_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..3ae243a3 100644 --- a/tests/unit/test_query.py +++ b/tests/unit/test_query.py @@ -14,13 +14,9 @@ import pickle -try: - from unittest import mock -except ImportError: # pragma: NO PY3 COVER - import mock +from unittest import mock import pytest -import six from google.cloud.datastore import entity as datastore_entity from google.cloud.datastore import helpers @@ -2306,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 @@ -2332,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 @@ -2358,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 @@ -2385,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) 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