diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index 76d0baa0..7d98291c 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -1,3 +1,3 @@ docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:4370ced27a324687ede5da07132dcdc5381993502a5e8a3e31e16dc631d026f0 + digest: sha256:58f73ba196b5414782605236dd0712a73541b44ff2ff4d3a36ec41092dd6fa5b diff --git a/.kokoro/samples/python3.10/common.cfg b/.kokoro/samples/python3.10/common.cfg new file mode 100644 index 00000000..237cfb14 --- /dev/null +++ b/.kokoro/samples/python3.10/common.cfg @@ -0,0 +1,40 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "py-3.10" +} + +# Declare build specific Cloud project. +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-310" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-datastore/.kokoro/test-samples.sh" +} + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download secrets for samples +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "python-datastore/.kokoro/trampoline_v2.sh" \ No newline at end of file diff --git a/.kokoro/samples/python3.10/continuous.cfg b/.kokoro/samples/python3.10/continuous.cfg new file mode 100644 index 00000000..a1c8d975 --- /dev/null +++ b/.kokoro/samples/python3.10/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.10/periodic-head.cfg b/.kokoro/samples/python3.10/periodic-head.cfg new file mode 100644 index 00000000..714045a7 --- /dev/null +++ b/.kokoro/samples/python3.10/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-datastore/.kokoro/test-samples-against-head.sh" +} diff --git a/.kokoro/samples/python3.10/periodic.cfg b/.kokoro/samples/python3.10/periodic.cfg new file mode 100644 index 00000000..71cd1e59 --- /dev/null +++ b/.kokoro/samples/python3.10/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} diff --git a/.kokoro/samples/python3.10/presubmit.cfg b/.kokoro/samples/python3.10/presubmit.cfg new file mode 100644 index 00000000..a1c8d975 --- /dev/null +++ b/.kokoro/samples/python3.10/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b81e228..b24a7b8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-cloud-datastore/#history +## [2.3.0](https://www.github.com/googleapis/python-datastore/compare/v2.2.0...v2.3.0) (2021-10-18) + + +### Features + +* add 'Client.entity' helper ([#239](https://www.github.com/googleapis/python-datastore/issues/239)) ([49d48f1](https://www.github.com/googleapis/python-datastore/commit/49d48f17b0c311b859b62a8bd0af8ebf8f7d5717)) + + +### Bug Fixes + +* improve type hints, mypy check ([#242](https://www.github.com/googleapis/python-datastore/issues/242)) ([6398bbc](https://www.github.com/googleapis/python-datastore/commit/6398bbcaf8a9d845a4b3d06cfc673a633851f48b)) + ## [2.2.0](https://www.github.com/googleapis/python-datastore/compare/v2.1.6...v2.2.0) (2021-10-08) diff --git a/google/cloud/datastore/_http.py b/google/cloud/datastore/_http.py index 9e13567b..9ea5aac8 100644 --- a/google/cloud/datastore/_http.py +++ b/google/cloud/datastore/_http.py @@ -14,10 +14,10 @@ """Connections to Google Cloud Datastore API servers.""" -from google.rpc import status_pb2 +from google.rpc import status_pb2 # type: ignore from google.cloud import _http as connection_module -from google.cloud import exceptions +from google.cloud import exceptions # type: ignore from google.cloud.datastore_v1.types import datastore as _datastore_pb2 diff --git a/google/cloud/datastore/client.py b/google/cloud/datastore/client.py index b5de0fab..207759cc 100644 --- a/google/cloud/datastore/client.py +++ b/google/cloud/datastore/client.py @@ -17,10 +17,10 @@ import warnings import google.api_core.client_options -from google.auth.credentials import AnonymousCredentials -from google.cloud._helpers import _LocalStack -from google.cloud._helpers import _determine_default_project as _base_default_project -from google.cloud.client import ClientWithProject +from google.auth.credentials import AnonymousCredentials # type: ignore +from google.cloud._helpers import _LocalStack # type: ignore +from google.cloud._helpers import _determine_default_project as _base_default_project # type: ignore +from google.cloud.client import ClientWithProject # type: ignore from google.cloud.datastore.version import __version__ from google.cloud.datastore import helpers from google.cloud.datastore._http import HTTPDatastoreAPI @@ -32,13 +32,14 @@ try: from google.cloud.datastore._gapic import make_datastore_api - except ImportError: # pragma: NO COVER - from google.api_core import client_info + from google.api_core import client_info as api_core_client_info + + def make_datastore_api(client): + raise RuntimeError("No gRPC available") - make_datastore_api = None _HAVE_GRPC = False - _CLIENT_INFO = client_info.ClientInfo(client_library_version=__version__) + _CLIENT_INFO = api_core_client_info.ClientInfo(client_library_version=__version__) else: from google.api_core.gapic_v1 import client_info @@ -737,6 +738,10 @@ def key(self, *path_args, **kwargs): kwargs["namespace"] = self.namespace return Key(*path_args, **kwargs) + def entity(self, key=None, exclude_from_indexes=()): + """Proxy to :class:`google.cloud.datastore.entity.Entity`.""" + return Entity(key=key, exclude_from_indexes=exclude_from_indexes) + def batch(self): """Proxy to :class:`google.cloud.datastore.batch.Batch`.""" return Batch(self) diff --git a/google/cloud/datastore/py.typed b/google/cloud/datastore/py.typed new file mode 100644 index 00000000..341f38ba --- /dev/null +++ b/google/cloud/datastore/py.typed @@ -0,0 +1,2 @@ +# Marker file for PEP 561. +# The google-cloud-datastore package uses inline types. diff --git a/google/cloud/datastore/version.py b/google/cloud/datastore/version.py index bd0f8e5c..999199f5 100644 --- a/google/cloud/datastore/version.py +++ b/google/cloud/datastore/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.2.0" +__version__ = "2.3.0" diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..5663b40d --- /dev/null +++ b/mypy.ini @@ -0,0 +1,7 @@ +[mypy] +python_version = 3.6 +namespace_packages = True +ignore_missing_imports = True + +[mypy-google.protobuf] +ignore_missing_imports = True diff --git a/noxfile.py b/noxfile.py index 1ca31940..4eeac549 100644 --- a/noxfile.py +++ b/noxfile.py @@ -37,6 +37,7 @@ nox.options.sessions = [ "unit", "system", + "mypy", "cover", "lint", "lint_setup_py", @@ -72,6 +73,15 @@ def blacken(session): ) +@nox.session(python=DEFAULT_PYTHON_VERSION) +def mypy(session): + """Verify type hints are mypy compatible.""" + session.install("-e", ".") + session.install("mypy") + # TODO: also verify types on tests, all of google package + session.run("mypy", "-p", "google.cloud.datastore", "--no-incremental") + + @nox.session(python=DEFAULT_PYTHON_VERSION) def lint_setup_py(session): """Verify that setup.py is valid (including RST check).""" diff --git a/owlbot.py b/owlbot.py index abbbb99a..0ad059b7 100644 --- a/owlbot.py +++ b/owlbot.py @@ -200,4 +200,37 @@ def docfx(session): """, ) +# add type checker nox session +s.replace("noxfile.py", + """nox.options.sessions = \[ + "unit", + "system",""", + """nox.options.sessions = [ + "unit", + "system", + "mypy",""", +) + + +s.replace( + "noxfile.py", + """\ +@nox.session\(python=DEFAULT_PYTHON_VERSION\) +def lint_setup_py\(session\): +""", + '''\ +@nox.session(python=DEFAULT_PYTHON_VERSION) +def mypy(session): + """Verify type hints are mypy compatible.""" + session.install("-e", ".") + session.install("mypy") + # TODO: also verify types on tests, all of google package + session.run("mypy", "-p", "google.cloud.datastore", "--no-incremental") + + +@nox.session(python=DEFAULT_PYTHON_VERSION) +def lint_setup_py(session): +''', +) + s.shell.run(["nox", "-s", "blacken"], hide_output=False) diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 5127fd60..f4c27cf4 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -1335,6 +1335,30 @@ def test_key_w_namespace_collision(self): kind, id_, project=self.PROJECT, namespace=namespace2 ) + def test_entity_w_defaults(self): + creds = _make_credentials() + client = self._make_one(credentials=creds) + + patch = mock.patch("google.cloud.datastore.client.Entity", spec=["__call__"]) + with patch as mock_klass: + entity = client.entity() + self.assertIs(entity, mock_klass.return_value) + mock_klass.assert_called_once_with(key=None, exclude_from_indexes=()) + + def test_entity_w_explicit(self): + key = mock.Mock(spec=[]) + exclude_from_indexes = ["foo", "bar"] + creds = _make_credentials() + client = self._make_one(credentials=creds) + + patch = mock.patch("google.cloud.datastore.client.Entity", spec=["__call__"]) + with patch as mock_klass: + entity = client.entity(key, exclude_from_indexes) + self.assertIs(entity, mock_klass.return_value) + mock_klass.assert_called_once_with( + key=key, exclude_from_indexes=exclude_from_indexes + ) + def test_batch(self): creds = _make_credentials() client = self._make_one(credentials=creds)