diff --git a/google/cloud/datastore_admin_v1/__init__.py b/google/cloud/datastore_admin_v1/__init__.py index 70a79c07..4d0164cf 100644 --- a/google/cloud/datastore_admin_v1/__init__.py +++ b/google/cloud/datastore_admin_v1/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ from .types.datastore_admin import CommonMetadata from .types.datastore_admin import CreateIndexRequest +from .types.datastore_admin import DatastoreFirestoreMigrationMetadata from .types.datastore_admin import DeleteIndexRequest from .types.datastore_admin import EntityFilter from .types.datastore_admin import ExportEntitiesMetadata @@ -33,12 +34,17 @@ from .types.datastore_admin import Progress from .types.datastore_admin import OperationType from .types.index import Index +from .types.migration import MigrationProgressEvent +from .types.migration import MigrationStateEvent +from .types.migration import MigrationState +from .types.migration import MigrationStep __all__ = ( "DatastoreAdminAsyncClient", "CommonMetadata", "CreateIndexRequest", "DatastoreAdminClient", + "DatastoreFirestoreMigrationMetadata", "DeleteIndexRequest", "EntityFilter", "ExportEntitiesMetadata", @@ -51,6 +57,10 @@ "IndexOperationMetadata", "ListIndexesRequest", "ListIndexesResponse", + "MigrationProgressEvent", + "MigrationState", + "MigrationStateEvent", + "MigrationStep", "OperationType", "Progress", ) diff --git a/google/cloud/datastore_admin_v1/services/__init__.py b/google/cloud/datastore_admin_v1/services/__init__.py index 4de65971..e8e1c384 100644 --- a/google/cloud/datastore_admin_v1/services/__init__.py +++ b/google/cloud/datastore_admin_v1/services/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/google/cloud/datastore_admin_v1/services/datastore_admin/__init__.py b/google/cloud/datastore_admin_v1/services/datastore_admin/__init__.py index 951a69a9..6e5bb3d1 100644 --- a/google/cloud/datastore_admin_v1/services/datastore_admin/__init__.py +++ b/google/cloud/datastore_admin_v1/services/datastore_admin/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/google/cloud/datastore_admin_v1/services/datastore_admin/async_client.py b/google/cloud/datastore_admin_v1/services/datastore_admin/async_client.py index c64a328c..ebac62bd 100644 --- a/google/cloud/datastore_admin_v1/services/datastore_admin/async_client.py +++ b/google/cloud/datastore_admin_v1/services/datastore_admin/async_client.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ from collections import OrderedDict import functools import re -from typing import Dict, Sequence, Tuple, Type, Union +from typing import Dict, Optional, Sequence, Tuple, Type, Union import pkg_resources from google.api_core.client_options import ClientOptions @@ -31,12 +31,12 @@ except AttributeError: # pragma: NO COVER OptionalRetry = Union[retries.Retry, object] # type: ignore -from google.api_core import operation -from google.api_core import operation_async +from google.api_core import operation # type: ignore +from google.api_core import operation_async # type: ignore from google.cloud.datastore_admin_v1.services.datastore_admin import pagers from google.cloud.datastore_admin_v1.types import datastore_admin from google.cloud.datastore_admin_v1.types import index -from google.protobuf import empty_pb2 +from google.protobuf import empty_pb2 # type: ignore from .transports.base import DatastoreAdminTransport, DEFAULT_CLIENT_INFO from .transports.grpc_asyncio import DatastoreAdminGrpcAsyncIOTransport from .client import DatastoreAdminClient @@ -166,6 +166,42 @@ def from_service_account_file(cls, filename: str, *args, **kwargs): from_service_account_json = from_service_account_file + @classmethod + def get_mtls_endpoint_and_cert_source( + cls, client_options: Optional[ClientOptions] = None + ): + """Return the API endpoint and client cert source for mutual TLS. + + The client cert source is determined in the following order: + (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the + client cert source is None. + (2) if `client_options.client_cert_source` is provided, use the provided one; if the + default client cert source exists, use the default one; otherwise the client cert + source is None. + + The API endpoint is determined in the following order: + (1) if `client_options.api_endpoint` if provided, use the provided one. + (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the + default mTLS endpoint; if the environment variabel is "never", use the default API + endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise + use the default API endpoint. + + More details can be found at https://google.aip.dev/auth/4114. + + Args: + client_options (google.api_core.client_options.ClientOptions): Custom options for the + client. Only the `api_endpoint` and `client_cert_source` properties may be used + in this method. + + Returns: + Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the + client cert source to use. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If any errors happen. + """ + return DatastoreAdminClient.get_mtls_endpoint_and_cert_source(client_options) # type: ignore + @property def transport(self) -> DatastoreAdminTransport: """Returns the transport used by the client instance. @@ -249,6 +285,31 @@ async def export_entities( before completion it may leave partial data behind in Google Cloud Storage. + + .. code-block:: python + + from google.cloud import datastore_admin_v1 + + def sample_export_entities(): + # Create a client + client = datastore_admin_v1.DatastoreAdminClient() + + # Initialize request argument(s) + request = datastore_admin_v1.ExportEntitiesRequest( + project_id="project_id_value", + output_url_prefix="output_url_prefix_value", + ) + + # Make the request + operation = client.export_entities(request=request) + + print("Waiting for operation to complete...") + + response = operation.result() + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_admin_v1.types.ExportEntitiesRequest, dict]): The request object. The request for @@ -316,7 +377,7 @@ async def export_entities( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any( [project_id, labels, entity_filter, output_url_prefix] @@ -383,6 +444,31 @@ async def import_entities( is possible that a subset of the data has already been imported to Cloud Datastore. + + .. code-block:: python + + from google.cloud import datastore_admin_v1 + + def sample_import_entities(): + # Create a client + client = datastore_admin_v1.DatastoreAdminClient() + + # Initialize request argument(s) + request = datastore_admin_v1.ImportEntitiesRequest( + project_id="project_id_value", + input_url="input_url_value", + ) + + # Make the request + operation = client.import_entities(request=request) + + print("Waiting for operation to complete...") + + response = operation.result() + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_admin_v1.types.ImportEntitiesRequest, dict]): The request object. The request for @@ -456,7 +542,7 @@ async def import_entities( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([project_id, labels, input_url, entity_filter]) if request is not None and has_flattened_params: @@ -525,6 +611,29 @@ async def create_index( Indexes with a single property cannot be created. + + .. code-block:: python + + from google.cloud import datastore_admin_v1 + + def sample_create_index(): + # Create a client + client = datastore_admin_v1.DatastoreAdminClient() + + # Initialize request argument(s) + request = datastore_admin_v1.CreateIndexRequest( + ) + + # Make the request + operation = client.create_index(request=request) + + print("Waiting for operation to complete...") + + response = operation.result() + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_admin_v1.types.CreateIndexRequest, dict]): The request object. The request for @@ -592,6 +701,29 @@ async def delete_index( [delete][google.datastore.admin.v1.DatastoreAdmin.DeleteIndex] again. + + .. code-block:: python + + from google.cloud import datastore_admin_v1 + + def sample_delete_index(): + # Create a client + client = datastore_admin_v1.DatastoreAdminClient() + + # Initialize request argument(s) + request = datastore_admin_v1.DeleteIndexRequest( + ) + + # Make the request + operation = client.delete_index(request=request) + + print("Waiting for operation to complete...") + + response = operation.result() + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_admin_v1.types.DeleteIndexRequest, dict]): The request object. The request for @@ -646,6 +778,24 @@ async def get_index( ) -> index.Index: r"""Gets an index. + .. code-block:: python + + from google.cloud import datastore_admin_v1 + + def sample_get_index(): + # Create a client + client = datastore_admin_v1.DatastoreAdminClient() + + # Initialize request argument(s) + request = datastore_admin_v1.GetIndexRequest( + ) + + # Make the request + response = client.get_index(request=request) + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_admin_v1.types.GetIndexRequest, dict]): The request object. The request for @@ -700,6 +850,26 @@ async def list_indexes( the list of indexes and may occasionally return stale results. + + .. code-block:: python + + from google.cloud import datastore_admin_v1 + + def sample_list_indexes(): + # Create a client + client = datastore_admin_v1.DatastoreAdminClient() + + # Initialize request argument(s) + request = datastore_admin_v1.ListIndexesRequest( + ) + + # Make the request + page_result = client.list_indexes(request=request) + + # Handle the response + for response in page_result: + print(response) + Args: request (Union[google.cloud.datastore_admin_v1.types.ListIndexesRequest, dict]): The request object. The request for diff --git a/google/cloud/datastore_admin_v1/services/datastore_admin/client.py b/google/cloud/datastore_admin_v1/services/datastore_admin/client.py index 695446c5..4f4f9211 100644 --- a/google/cloud/datastore_admin_v1/services/datastore_admin/client.py +++ b/google/cloud/datastore_admin_v1/services/datastore_admin/client.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -34,12 +34,12 @@ except AttributeError: # pragma: NO COVER OptionalRetry = Union[retries.Retry, object] # type: ignore -from google.api_core import operation -from google.api_core import operation_async +from google.api_core import operation # type: ignore +from google.api_core import operation_async # type: ignore from google.cloud.datastore_admin_v1.services.datastore_admin import pagers from google.cloud.datastore_admin_v1.types import datastore_admin from google.cloud.datastore_admin_v1.types import index -from google.protobuf import empty_pb2 +from google.protobuf import empty_pb2 # type: ignore from .transports.base import DatastoreAdminTransport, DEFAULT_CLIENT_INFO from .transports.grpc import DatastoreAdminGrpcTransport from .transports.grpc_asyncio import DatastoreAdminGrpcAsyncIOTransport @@ -277,6 +277,73 @@ def parse_common_location_path(path: str) -> Dict[str, str]: m = re.match(r"^projects/(?P.+?)/locations/(?P.+?)$", path) return m.groupdict() if m else {} + @classmethod + def get_mtls_endpoint_and_cert_source( + cls, client_options: Optional[client_options_lib.ClientOptions] = None + ): + """Return the API endpoint and client cert source for mutual TLS. + + The client cert source is determined in the following order: + (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the + client cert source is None. + (2) if `client_options.client_cert_source` is provided, use the provided one; if the + default client cert source exists, use the default one; otherwise the client cert + source is None. + + The API endpoint is determined in the following order: + (1) if `client_options.api_endpoint` if provided, use the provided one. + (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the + default mTLS endpoint; if the environment variabel is "never", use the default API + endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise + use the default API endpoint. + + More details can be found at https://google.aip.dev/auth/4114. + + Args: + client_options (google.api_core.client_options.ClientOptions): Custom options for the + client. Only the `api_endpoint` and `client_cert_source` properties may be used + in this method. + + Returns: + Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the + client cert source to use. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If any errors happen. + """ + if client_options is None: + client_options = client_options_lib.ClientOptions() + use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") + if use_client_cert not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) + if use_mtls_endpoint not in ("auto", "never", "always"): + raise MutualTLSChannelError( + "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" + ) + + # Figure out the client cert source to use. + client_cert_source = None + if use_client_cert == "true": + if client_options.client_cert_source: + client_cert_source = client_options.client_cert_source + elif mtls.has_default_client_cert_source(): + client_cert_source = mtls.default_client_cert_source() + + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + elif use_mtls_endpoint == "always" or ( + use_mtls_endpoint == "auto" and client_cert_source + ): + api_endpoint = cls.DEFAULT_MTLS_ENDPOINT + else: + api_endpoint = cls.DEFAULT_ENDPOINT + + return api_endpoint, client_cert_source + def __init__( self, *, @@ -327,57 +394,22 @@ def __init__( if client_options is None: client_options = client_options_lib.ClientOptions() - # Create SSL credentials for mutual TLS if needed. - if os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") not in ( - "true", - "false", - ): - raise ValueError( - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" - ) - use_client_cert = ( - os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true" + api_endpoint, client_cert_source_func = self.get_mtls_endpoint_and_cert_source( + client_options ) - client_cert_source_func = None - is_mtls = False - if use_client_cert: - if client_options.client_cert_source: - is_mtls = True - client_cert_source_func = client_options.client_cert_source - else: - is_mtls = mtls.has_default_client_cert_source() - if is_mtls: - client_cert_source_func = mtls.default_client_cert_source() - else: - client_cert_source_func = None - - # Figure out which api endpoint to use. - if client_options.api_endpoint is not None: - api_endpoint = client_options.api_endpoint - else: - use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") - if use_mtls_env == "never": - api_endpoint = self.DEFAULT_ENDPOINT - elif use_mtls_env == "always": - api_endpoint = self.DEFAULT_MTLS_ENDPOINT - elif use_mtls_env == "auto": - if is_mtls: - api_endpoint = self.DEFAULT_MTLS_ENDPOINT - else: - api_endpoint = self.DEFAULT_ENDPOINT - else: - raise MutualTLSChannelError( - "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted " - "values: never, auto, always" - ) + api_key_value = getattr(client_options, "api_key", None) + if api_key_value and credentials: + raise ValueError( + "client_options.api_key and credentials are mutually exclusive" + ) # Save or instantiate the transport. # Ordinarily, we provide the transport, but allowing a custom transport # instance provides an extensibility point for unusual situations. if isinstance(transport, DatastoreAdminTransport): # transport is a DatastoreAdminTransport instance. - if credentials or client_options.credentials_file: + if credentials or client_options.credentials_file or api_key_value: raise ValueError( "When providing a transport instance, " "provide its credentials directly." @@ -389,6 +421,15 @@ def __init__( ) self._transport = transport else: + import google.auth._default # type: ignore + + if api_key_value and hasattr( + google.auth._default, "get_api_key_credentials" + ): + credentials = google.auth._default.get_api_key_credentials( + api_key_value + ) + Transport = type(self).get_transport_class(transport) self._transport = Transport( credentials=credentials, @@ -424,6 +465,31 @@ def export_entities( before completion it may leave partial data behind in Google Cloud Storage. + + .. code-block:: python + + from google.cloud import datastore_admin_v1 + + def sample_export_entities(): + # Create a client + client = datastore_admin_v1.DatastoreAdminClient() + + # Initialize request argument(s) + request = datastore_admin_v1.ExportEntitiesRequest( + project_id="project_id_value", + output_url_prefix="output_url_prefix_value", + ) + + # Make the request + operation = client.export_entities(request=request) + + print("Waiting for operation to complete...") + + response = operation.result() + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_admin_v1.types.ExportEntitiesRequest, dict]): The request object. The request for @@ -491,7 +557,7 @@ def export_entities( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any( [project_id, labels, entity_filter, output_url_prefix] @@ -557,6 +623,31 @@ def import_entities( is possible that a subset of the data has already been imported to Cloud Datastore. + + .. code-block:: python + + from google.cloud import datastore_admin_v1 + + def sample_import_entities(): + # Create a client + client = datastore_admin_v1.DatastoreAdminClient() + + # Initialize request argument(s) + request = datastore_admin_v1.ImportEntitiesRequest( + project_id="project_id_value", + input_url="input_url_value", + ) + + # Make the request + operation = client.import_entities(request=request) + + print("Waiting for operation to complete...") + + response = operation.result() + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_admin_v1.types.ImportEntitiesRequest, dict]): The request object. The request for @@ -630,7 +721,7 @@ def import_entities( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([project_id, labels, input_url, entity_filter]) if request is not None and has_flattened_params: @@ -698,6 +789,29 @@ def create_index( Indexes with a single property cannot be created. + + .. code-block:: python + + from google.cloud import datastore_admin_v1 + + def sample_create_index(): + # Create a client + client = datastore_admin_v1.DatastoreAdminClient() + + # Initialize request argument(s) + request = datastore_admin_v1.CreateIndexRequest( + ) + + # Make the request + operation = client.create_index(request=request) + + print("Waiting for operation to complete...") + + response = operation.result() + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_admin_v1.types.CreateIndexRequest, dict]): The request object. The request for @@ -766,6 +880,29 @@ def delete_index( [delete][google.datastore.admin.v1.DatastoreAdmin.DeleteIndex] again. + + .. code-block:: python + + from google.cloud import datastore_admin_v1 + + def sample_delete_index(): + # Create a client + client = datastore_admin_v1.DatastoreAdminClient() + + # Initialize request argument(s) + request = datastore_admin_v1.DeleteIndexRequest( + ) + + # Make the request + operation = client.delete_index(request=request) + + print("Waiting for operation to complete...") + + response = operation.result() + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_admin_v1.types.DeleteIndexRequest, dict]): The request object. The request for @@ -821,6 +958,24 @@ def get_index( ) -> index.Index: r"""Gets an index. + .. code-block:: python + + from google.cloud import datastore_admin_v1 + + def sample_get_index(): + # Create a client + client = datastore_admin_v1.DatastoreAdminClient() + + # Initialize request argument(s) + request = datastore_admin_v1.GetIndexRequest( + ) + + # Make the request + response = client.get_index(request=request) + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_admin_v1.types.GetIndexRequest, dict]): The request object. The request for @@ -866,6 +1021,26 @@ def list_indexes( the list of indexes and may occasionally return stale results. + + .. code-block:: python + + from google.cloud import datastore_admin_v1 + + def sample_list_indexes(): + # Create a client + client = datastore_admin_v1.DatastoreAdminClient() + + # Initialize request argument(s) + request = datastore_admin_v1.ListIndexesRequest( + ) + + # Make the request + page_result = client.list_indexes(request=request) + + # Handle the response + for response in page_result: + print(response) + Args: request (Union[google.cloud.datastore_admin_v1.types.ListIndexesRequest, dict]): The request object. The request for diff --git a/google/cloud/datastore_admin_v1/services/datastore_admin/pagers.py b/google/cloud/datastore_admin_v1/services/datastore_admin/pagers.py index a2f14858..9a2d05ad 100644 --- a/google/cloud/datastore_admin_v1/services/datastore_admin/pagers.py +++ b/google/cloud/datastore_admin_v1/services/datastore_admin/pagers.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/google/cloud/datastore_admin_v1/services/datastore_admin/transports/__init__.py b/google/cloud/datastore_admin_v1/services/datastore_admin/transports/__init__.py index 376bbfa1..7d7ea9d4 100644 --- a/google/cloud/datastore_admin_v1/services/datastore_admin/transports/__init__.py +++ b/google/cloud/datastore_admin_v1/services/datastore_admin/transports/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/google/cloud/datastore_admin_v1/services/datastore_admin/transports/base.py b/google/cloud/datastore_admin_v1/services/datastore_admin/transports/base.py index 58358b0a..1b47ae2b 100644 --- a/google/cloud/datastore_admin_v1/services/datastore_admin/transports/base.py +++ b/google/cloud/datastore_admin_v1/services/datastore_admin/transports/base.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ from google.cloud.datastore_admin_v1.types import datastore_admin from google.cloud.datastore_admin_v1.types import index -from google.longrunning import operations_pb2 +from google.longrunning import operations_pb2 # type: ignore try: DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( @@ -107,7 +107,6 @@ def __init__( credentials, _ = google.auth.load_credentials_from_file( credentials_file, **scopes_kwargs, quota_project_id=quota_project_id ) - elif credentials is None: credentials, _ = google.auth.default( **scopes_kwargs, quota_project_id=quota_project_id diff --git a/google/cloud/datastore_admin_v1/services/datastore_admin/transports/grpc.py b/google/cloud/datastore_admin_v1/services/datastore_admin/transports/grpc.py index d7018fcb..e27734f8 100644 --- a/google/cloud/datastore_admin_v1/services/datastore_admin/transports/grpc.py +++ b/google/cloud/datastore_admin_v1/services/datastore_admin/transports/grpc.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,11 +23,11 @@ from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore -import grpc +import grpc # type: ignore from google.cloud.datastore_admin_v1.types import datastore_admin from google.cloud.datastore_admin_v1.types import index -from google.longrunning import operations_pb2 +from google.longrunning import operations_pb2 # type: ignore from .base import DatastoreAdminTransport, DEFAULT_CLIENT_INFO @@ -219,8 +219,11 @@ def __init__( if not self._grpc_channel: self._grpc_channel = type(self).create_channel( self._host, + # use the credentials which are saved credentials=self._credentials, - credentials_file=credentials_file, + # Set ``credentials_file`` to ``None`` here as + # the credentials that we saved earlier should be used. + credentials_file=None, scopes=self._scopes, ssl_credentials=self._ssl_channel_credentials, quota_project_id=quota_project_id, @@ -293,7 +296,7 @@ def operations_client(self) -> operations_v1.OperationsClient: This property caches on the instance; repeated calls return the same client. """ - # Sanity check: Only create a new client if we do not already have one. + # Quick check: Only create a new client if we do not already have one. if self._operations_client is None: self._operations_client = operations_v1.OperationsClient(self.grpc_channel) diff --git a/google/cloud/datastore_admin_v1/services/datastore_admin/transports/grpc_asyncio.py b/google/cloud/datastore_admin_v1/services/datastore_admin/transports/grpc_asyncio.py index 84b5299b..46f84887 100644 --- a/google/cloud/datastore_admin_v1/services/datastore_admin/transports/grpc_asyncio.py +++ b/google/cloud/datastore_admin_v1/services/datastore_admin/transports/grpc_asyncio.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -22,12 +22,12 @@ from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore -import grpc -from grpc.experimental import aio +import grpc # type: ignore +from grpc.experimental import aio # type: ignore from google.cloud.datastore_admin_v1.types import datastore_admin from google.cloud.datastore_admin_v1.types import index -from google.longrunning import operations_pb2 +from google.longrunning import operations_pb2 # type: ignore from .base import DatastoreAdminTransport, DEFAULT_CLIENT_INFO from .grpc import DatastoreAdminGrpcTransport @@ -264,8 +264,11 @@ def __init__( if not self._grpc_channel: self._grpc_channel = type(self).create_channel( self._host, + # use the credentials which are saved credentials=self._credentials, - credentials_file=credentials_file, + # Set ``credentials_file`` to ``None`` here as + # the credentials that we saved earlier should be used. + credentials_file=None, scopes=self._scopes, ssl_credentials=self._ssl_channel_credentials, quota_project_id=quota_project_id, @@ -295,7 +298,7 @@ def operations_client(self) -> operations_v1.OperationsAsyncClient: This property caches on the instance; repeated calls return the same client. """ - # Sanity check: Only create a new client if we do not already have one. + # Quick check: Only create a new client if we do not already have one. if self._operations_client is None: self._operations_client = operations_v1.OperationsAsyncClient( self.grpc_channel diff --git a/google/cloud/datastore_admin_v1/types/__init__.py b/google/cloud/datastore_admin_v1/types/__init__.py index ac4ff905..fbc4f65f 100644 --- a/google/cloud/datastore_admin_v1/types/__init__.py +++ b/google/cloud/datastore_admin_v1/types/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ from .datastore_admin import ( CommonMetadata, CreateIndexRequest, + DatastoreFirestoreMigrationMetadata, DeleteIndexRequest, EntityFilter, ExportEntitiesMetadata, @@ -31,10 +32,17 @@ OperationType, ) from .index import Index +from .migration import ( + MigrationProgressEvent, + MigrationStateEvent, + MigrationState, + MigrationStep, +) __all__ = ( "CommonMetadata", "CreateIndexRequest", + "DatastoreFirestoreMigrationMetadata", "DeleteIndexRequest", "EntityFilter", "ExportEntitiesMetadata", @@ -49,4 +57,8 @@ "Progress", "OperationType", "Index", + "MigrationProgressEvent", + "MigrationStateEvent", + "MigrationState", + "MigrationStep", ) diff --git a/google/cloud/datastore_admin_v1/types/datastore_admin.py b/google/cloud/datastore_admin_v1/types/datastore_admin.py index 0d42d802..4e5ad0da 100644 --- a/google/cloud/datastore_admin_v1/types/datastore_admin.py +++ b/google/cloud/datastore_admin_v1/types/datastore_admin.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,10 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. # -import proto +import proto # type: ignore from google.cloud.datastore_admin_v1.types import index as gda_index -from google.protobuf import timestamp_pb2 +from google.cloud.datastore_admin_v1.types import migration +from google.protobuf import timestamp_pb2 # type: ignore __protobuf__ = proto.module( @@ -37,6 +38,7 @@ "ListIndexesRequest", "ListIndexesResponse", "IndexOperationMetadata", + "DatastoreFirestoreMigrationMetadata", }, ) @@ -410,4 +412,27 @@ class IndexOperationMetadata(proto.Message): index_id = proto.Field(proto.STRING, number=3,) +class DatastoreFirestoreMigrationMetadata(proto.Message): + r"""Metadata for Datastore to Firestore migration operations. + + The DatastoreFirestoreMigration operation is not started by the + end-user via an explicit "creation" method. This is an intentional + deviation from the LRO design pattern. + + This singleton resource can be accessed at: + ``projects/{project_id}/datastore-firestore-migration`` + + Attributes: + migration_state (google.cloud.datastore_admin_v1.types.MigrationState): + The current state of migration from Cloud + Datastore to Cloud Firestore in Datastore mode. + migration_step (google.cloud.datastore_admin_v1.types.MigrationStep): + The current step of migration from Cloud + Datastore to Cloud Firestore in Datastore mode. + """ + + migration_state = proto.Field(proto.ENUM, number=1, enum=migration.MigrationState,) + migration_step = proto.Field(proto.ENUM, number=2, enum=migration.MigrationStep,) + + __all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/datastore_admin_v1/types/index.py b/google/cloud/datastore_admin_v1/types/index.py index 7a57c945..8d50f03a 100644 --- a/google/cloud/datastore_admin_v1/types/index.py +++ b/google/cloud/datastore_admin_v1/types/index.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -import proto +import proto # type: ignore __protobuf__ = proto.module(package="google.datastore.admin.v1", manifest={"Index",},) diff --git a/google/cloud/datastore_admin_v1/types/migration.py b/google/cloud/datastore_admin_v1/types/migration.py new file mode 100644 index 00000000..18cdd8d6 --- /dev/null +++ b/google/cloud/datastore_admin_v1/types/migration.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import proto # type: ignore + + +__protobuf__ = proto.module( + package="google.datastore.admin.v1", + manifest={ + "MigrationState", + "MigrationStep", + "MigrationStateEvent", + "MigrationProgressEvent", + }, +) + + +class MigrationState(proto.Enum): + r"""States for a migration.""" + MIGRATION_STATE_UNSPECIFIED = 0 + RUNNING = 1 + PAUSED = 2 + COMPLETE = 3 + + +class MigrationStep(proto.Enum): + r"""Steps in a migration.""" + MIGRATION_STEP_UNSPECIFIED = 0 + PREPARE = 6 + START = 1 + APPLY_WRITES_SYNCHRONOUSLY = 7 + COPY_AND_VERIFY = 2 + REDIRECT_EVENTUALLY_CONSISTENT_READS = 3 + REDIRECT_STRONGLY_CONSISTENT_READS = 4 + REDIRECT_WRITES = 5 + + +class MigrationStateEvent(proto.Message): + r"""An event signifying a change in state of a `migration from Cloud + Datastore to Cloud Firestore in Datastore + mode `__. + + Attributes: + state (google.cloud.datastore_admin_v1.types.MigrationState): + The new state of the migration. + """ + + state = proto.Field(proto.ENUM, number=1, enum="MigrationState",) + + +class MigrationProgressEvent(proto.Message): + r"""An event signifying the start of a new step in a `migration from + Cloud Datastore to Cloud Firestore in Datastore + mode `__. + + This message has `oneof`_ fields (mutually exclusive fields). + For each oneof, at most one member field can be set at the same time. + Setting any member of the oneof automatically clears all other + members. + + .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields + + Attributes: + step (google.cloud.datastore_admin_v1.types.MigrationStep): + The step that is starting. + + An event with step set to ``START`` indicates that the + migration has been reverted back to the initial + pre-migration state. + prepare_step_details (google.cloud.datastore_admin_v1.types.MigrationProgressEvent.PrepareStepDetails): + Details for the ``PREPARE`` step. + + This field is a member of `oneof`_ ``step_details``. + redirect_writes_step_details (google.cloud.datastore_admin_v1.types.MigrationProgressEvent.RedirectWritesStepDetails): + Details for the ``REDIRECT_WRITES`` step. + + This field is a member of `oneof`_ ``step_details``. + """ + + class ConcurrencyMode(proto.Enum): + r"""Concurrency modes for transactions in Cloud Firestore.""" + CONCURRENCY_MODE_UNSPECIFIED = 0 + PESSIMISTIC = 1 + OPTIMISTIC = 2 + + class PrepareStepDetails(proto.Message): + r"""Details for the ``PREPARE`` step. + + Attributes: + concurrency_mode (google.cloud.datastore_admin_v1.types.MigrationProgressEvent.ConcurrencyMode): + The concurrency mode this database will use when it reaches + the ``REDIRECT_WRITES`` step. + """ + + concurrency_mode = proto.Field( + proto.ENUM, number=1, enum="MigrationProgressEvent.ConcurrencyMode", + ) + + class RedirectWritesStepDetails(proto.Message): + r"""Details for the ``REDIRECT_WRITES`` step. + + Attributes: + concurrency_mode (google.cloud.datastore_admin_v1.types.MigrationProgressEvent.ConcurrencyMode): + Ths concurrency mode for this database. + """ + + concurrency_mode = proto.Field( + proto.ENUM, number=1, enum="MigrationProgressEvent.ConcurrencyMode", + ) + + step = proto.Field(proto.ENUM, number=1, enum="MigrationStep",) + prepare_step_details = proto.Field( + proto.MESSAGE, number=2, oneof="step_details", message=PrepareStepDetails, + ) + redirect_writes_step_details = proto.Field( + proto.MESSAGE, + number=3, + oneof="step_details", + message=RedirectWritesStepDetails, + ) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/datastore_v1/__init__.py b/google/cloud/datastore_v1/__init__.py index 247eec15..881df4ca 100644 --- a/google/cloud/datastore_v1/__init__.py +++ b/google/cloud/datastore_v1/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/google/cloud/datastore_v1/services/__init__.py b/google/cloud/datastore_v1/services/__init__.py index 4de65971..e8e1c384 100644 --- a/google/cloud/datastore_v1/services/__init__.py +++ b/google/cloud/datastore_v1/services/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/google/cloud/datastore_v1/services/datastore/__init__.py b/google/cloud/datastore_v1/services/datastore/__init__.py index 611f280b..66d6560d 100644 --- a/google/cloud/datastore_v1/services/datastore/__init__.py +++ b/google/cloud/datastore_v1/services/datastore/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/google/cloud/datastore_v1/services/datastore/async_client.py b/google/cloud/datastore_v1/services/datastore/async_client.py index 2246300e..c6f8431b 100644 --- a/google/cloud/datastore_v1/services/datastore/async_client.py +++ b/google/cloud/datastore_v1/services/datastore/async_client.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ from collections import OrderedDict import functools import re -from typing import Dict, Sequence, Tuple, Type, Union +from typing import Dict, Optional, Sequence, Tuple, Type, Union import pkg_resources from google.api_core.client_options import ClientOptions @@ -106,6 +106,42 @@ def from_service_account_file(cls, filename: str, *args, **kwargs): from_service_account_json = from_service_account_file + @classmethod + def get_mtls_endpoint_and_cert_source( + cls, client_options: Optional[ClientOptions] = None + ): + """Return the API endpoint and client cert source for mutual TLS. + + The client cert source is determined in the following order: + (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the + client cert source is None. + (2) if `client_options.client_cert_source` is provided, use the provided one; if the + default client cert source exists, use the default one; otherwise the client cert + source is None. + + The API endpoint is determined in the following order: + (1) if `client_options.api_endpoint` if provided, use the provided one. + (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the + default mTLS endpoint; if the environment variabel is "never", use the default API + endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise + use the default API endpoint. + + More details can be found at https://google.aip.dev/auth/4114. + + Args: + client_options (google.api_core.client_options.ClientOptions): Custom options for the + client. Only the `api_endpoint` and `client_cert_source` properties may be used + in this method. + + Returns: + Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the + client cert source to use. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If any errors happen. + """ + return DatastoreClient.get_mtls_endpoint_and_cert_source(client_options) # type: ignore + @property def transport(self) -> DatastoreTransport: """Returns the transport used by the client instance. @@ -179,6 +215,25 @@ async def lookup( ) -> datastore.LookupResponse: r"""Looks up entities by key. + .. code-block:: python + + from google.cloud import datastore_v1 + + def sample_lookup(): + # Create a client + client = datastore_v1.DatastoreClient() + + # Initialize request argument(s) + request = datastore_v1.LookupRequest( + project_id="project_id_value", + ) + + # Make the request + response = client.lookup(request=request) + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_v1.types.LookupRequest, dict]): The request object. The request for @@ -215,7 +270,7 @@ async def lookup( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([project_id, read_options, keys]) if request is not None and has_flattened_params: @@ -269,6 +324,25 @@ async def run_query( ) -> datastore.RunQueryResponse: r"""Queries for entities. + .. code-block:: python + + from google.cloud import datastore_v1 + + def sample_run_query(): + # Create a client + client = datastore_v1.DatastoreClient() + + # Initialize request argument(s) + request = datastore_v1.RunQueryRequest( + project_id="project_id_value", + ) + + # Make the request + response = client.run_query(request=request) + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_v1.types.RunQueryRequest, dict]): The request object. The request for @@ -323,6 +397,25 @@ async def begin_transaction( ) -> datastore.BeginTransactionResponse: r"""Begins a new transaction. + .. code-block:: python + + from google.cloud import datastore_v1 + + def sample_begin_transaction(): + # Create a client + client = datastore_v1.DatastoreClient() + + # Initialize request argument(s) + request = datastore_v1.BeginTransactionRequest( + project_id="project_id_value", + ) + + # Make the request + response = client.begin_transaction(request=request) + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_v1.types.BeginTransactionRequest, dict]): The request object. The request for @@ -347,7 +440,7 @@ async def begin_transaction( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([project_id]) if request is not None and has_flattened_params: @@ -392,6 +485,27 @@ async def commit( r"""Commits a transaction, optionally creating, deleting or modifying some entities. + + .. code-block:: python + + from google.cloud import datastore_v1 + + def sample_commit(): + # Create a client + client = datastore_v1.DatastoreClient() + + # Initialize request argument(s) + request = datastore_v1.CommitRequest( + transaction=b'transaction_blob', + project_id="project_id_value", + ) + + # Make the request + response = client.commit(request=request) + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_v1.types.CommitRequest, dict]): The request object. The request for @@ -451,7 +565,7 @@ async def commit( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([project_id, mode, transaction, mutations]) if request is not None and has_flattened_params: @@ -499,6 +613,26 @@ async def rollback( ) -> datastore.RollbackResponse: r"""Rolls back a transaction. + .. code-block:: python + + from google.cloud import datastore_v1 + + def sample_rollback(): + # Create a client + client = datastore_v1.DatastoreClient() + + # Initialize request argument(s) + request = datastore_v1.RollbackRequest( + project_id="project_id_value", + transaction=b'transaction_blob', + ) + + # Make the request + response = client.rollback(request=request) + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_v1.types.RollbackRequest, dict]): The request object. The request for @@ -531,7 +665,7 @@ async def rollback( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([project_id, transaction]) if request is not None and has_flattened_params: @@ -576,6 +710,26 @@ async def allocate_ids( r"""Allocates IDs for the given keys, which is useful for referencing an entity before it is inserted. + + .. code-block:: python + + from google.cloud import datastore_v1 + + def sample_allocate_ids(): + # Create a client + client = datastore_v1.DatastoreClient() + + # Initialize request argument(s) + request = datastore_v1.AllocateIdsRequest( + project_id="project_id_value", + ) + + # Make the request + response = client.allocate_ids(request=request) + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_v1.types.AllocateIdsRequest, dict]): The request object. The request for @@ -609,7 +763,7 @@ async def allocate_ids( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([project_id, keys]) if request is not None and has_flattened_params: @@ -651,8 +805,28 @@ async def reserve_ids( timeout: float = None, metadata: Sequence[Tuple[str, str]] = (), ) -> datastore.ReserveIdsResponse: - r"""Prevents the supplied keys' IDs from being auto- - llocated by Cloud Datastore. + r"""Prevents the supplied keys' IDs from being + auto-allocated by Cloud Datastore. + + + .. code-block:: python + + from google.cloud import datastore_v1 + + def sample_reserve_ids(): + # Create a client + client = datastore_v1.DatastoreClient() + + # Initialize request argument(s) + request = datastore_v1.ReserveIdsRequest( + project_id="project_id_value", + ) + + # Make the request + response = client.reserve_ids(request=request) + + # Handle the response + print(response) Args: request (Union[google.cloud.datastore_v1.types.ReserveIdsRequest, dict]): @@ -686,7 +860,7 @@ async def reserve_ids( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([project_id, keys]) if request is not None and has_flattened_params: diff --git a/google/cloud/datastore_v1/services/datastore/client.py b/google/cloud/datastore_v1/services/datastore/client.py index 5205b893..49c741de 100644 --- a/google/cloud/datastore_v1/services/datastore/client.py +++ b/google/cloud/datastore_v1/services/datastore/client.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -224,6 +224,73 @@ def parse_common_location_path(path: str) -> Dict[str, str]: m = re.match(r"^projects/(?P.+?)/locations/(?P.+?)$", path) return m.groupdict() if m else {} + @classmethod + def get_mtls_endpoint_and_cert_source( + cls, client_options: Optional[client_options_lib.ClientOptions] = None + ): + """Return the API endpoint and client cert source for mutual TLS. + + The client cert source is determined in the following order: + (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the + client cert source is None. + (2) if `client_options.client_cert_source` is provided, use the provided one; if the + default client cert source exists, use the default one; otherwise the client cert + source is None. + + The API endpoint is determined in the following order: + (1) if `client_options.api_endpoint` if provided, use the provided one. + (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the + default mTLS endpoint; if the environment variabel is "never", use the default API + endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise + use the default API endpoint. + + More details can be found at https://google.aip.dev/auth/4114. + + Args: + client_options (google.api_core.client_options.ClientOptions): Custom options for the + client. Only the `api_endpoint` and `client_cert_source` properties may be used + in this method. + + Returns: + Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the + client cert source to use. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If any errors happen. + """ + if client_options is None: + client_options = client_options_lib.ClientOptions() + use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") + if use_client_cert not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) + if use_mtls_endpoint not in ("auto", "never", "always"): + raise MutualTLSChannelError( + "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" + ) + + # Figure out the client cert source to use. + client_cert_source = None + if use_client_cert == "true": + if client_options.client_cert_source: + client_cert_source = client_options.client_cert_source + elif mtls.has_default_client_cert_source(): + client_cert_source = mtls.default_client_cert_source() + + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + elif use_mtls_endpoint == "always" or ( + use_mtls_endpoint == "auto" and client_cert_source + ): + api_endpoint = cls.DEFAULT_MTLS_ENDPOINT + else: + api_endpoint = cls.DEFAULT_ENDPOINT + + return api_endpoint, client_cert_source + def __init__( self, *, @@ -274,57 +341,22 @@ def __init__( if client_options is None: client_options = client_options_lib.ClientOptions() - # Create SSL credentials for mutual TLS if needed. - if os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") not in ( - "true", - "false", - ): - raise ValueError( - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" - ) - use_client_cert = ( - os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true" + api_endpoint, client_cert_source_func = self.get_mtls_endpoint_and_cert_source( + client_options ) - client_cert_source_func = None - is_mtls = False - if use_client_cert: - if client_options.client_cert_source: - is_mtls = True - client_cert_source_func = client_options.client_cert_source - else: - is_mtls = mtls.has_default_client_cert_source() - if is_mtls: - client_cert_source_func = mtls.default_client_cert_source() - else: - client_cert_source_func = None - - # Figure out which api endpoint to use. - if client_options.api_endpoint is not None: - api_endpoint = client_options.api_endpoint - else: - use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") - if use_mtls_env == "never": - api_endpoint = self.DEFAULT_ENDPOINT - elif use_mtls_env == "always": - api_endpoint = self.DEFAULT_MTLS_ENDPOINT - elif use_mtls_env == "auto": - if is_mtls: - api_endpoint = self.DEFAULT_MTLS_ENDPOINT - else: - api_endpoint = self.DEFAULT_ENDPOINT - else: - raise MutualTLSChannelError( - "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted " - "values: never, auto, always" - ) + api_key_value = getattr(client_options, "api_key", None) + if api_key_value and credentials: + raise ValueError( + "client_options.api_key and credentials are mutually exclusive" + ) # Save or instantiate the transport. # Ordinarily, we provide the transport, but allowing a custom transport # instance provides an extensibility point for unusual situations. if isinstance(transport, DatastoreTransport): # transport is a DatastoreTransport instance. - if credentials or client_options.credentials_file: + if credentials or client_options.credentials_file or api_key_value: raise ValueError( "When providing a transport instance, " "provide its credentials directly." @@ -336,6 +368,15 @@ def __init__( ) self._transport = transport else: + import google.auth._default # type: ignore + + if api_key_value and hasattr( + google.auth._default, "get_api_key_credentials" + ): + credentials = google.auth._default.get_api_key_credentials( + api_key_value + ) + Transport = type(self).get_transport_class(transport) self._transport = Transport( credentials=credentials, @@ -361,6 +402,25 @@ def lookup( ) -> datastore.LookupResponse: r"""Looks up entities by key. + .. code-block:: python + + from google.cloud import datastore_v1 + + def sample_lookup(): + # Create a client + client = datastore_v1.DatastoreClient() + + # Initialize request argument(s) + request = datastore_v1.LookupRequest( + project_id="project_id_value", + ) + + # Make the request + response = client.lookup(request=request) + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_v1.types.LookupRequest, dict]): The request object. The request for @@ -397,7 +457,7 @@ def lookup( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([project_id, read_options, keys]) if request is not None and has_flattened_params: @@ -441,6 +501,25 @@ def run_query( ) -> datastore.RunQueryResponse: r"""Queries for entities. + .. code-block:: python + + from google.cloud import datastore_v1 + + def sample_run_query(): + # Create a client + client = datastore_v1.DatastoreClient() + + # Initialize request argument(s) + request = datastore_v1.RunQueryRequest( + project_id="project_id_value", + ) + + # Make the request + response = client.run_query(request=request) + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_v1.types.RunQueryRequest, dict]): The request object. The request for @@ -486,6 +565,25 @@ def begin_transaction( ) -> datastore.BeginTransactionResponse: r"""Begins a new transaction. + .. code-block:: python + + from google.cloud import datastore_v1 + + def sample_begin_transaction(): + # Create a client + client = datastore_v1.DatastoreClient() + + # Initialize request argument(s) + request = datastore_v1.BeginTransactionRequest( + project_id="project_id_value", + ) + + # Make the request + response = client.begin_transaction(request=request) + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_v1.types.BeginTransactionRequest, dict]): The request object. The request for @@ -510,7 +608,7 @@ def begin_transaction( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([project_id]) if request is not None and has_flattened_params: @@ -555,6 +653,27 @@ def commit( r"""Commits a transaction, optionally creating, deleting or modifying some entities. + + .. code-block:: python + + from google.cloud import datastore_v1 + + def sample_commit(): + # Create a client + client = datastore_v1.DatastoreClient() + + # Initialize request argument(s) + request = datastore_v1.CommitRequest( + transaction=b'transaction_blob', + project_id="project_id_value", + ) + + # Make the request + response = client.commit(request=request) + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_v1.types.CommitRequest, dict]): The request object. The request for @@ -614,7 +733,7 @@ def commit( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([project_id, mode, transaction, mutations]) if request is not None and has_flattened_params: @@ -662,6 +781,26 @@ def rollback( ) -> datastore.RollbackResponse: r"""Rolls back a transaction. + .. code-block:: python + + from google.cloud import datastore_v1 + + def sample_rollback(): + # Create a client + client = datastore_v1.DatastoreClient() + + # Initialize request argument(s) + request = datastore_v1.RollbackRequest( + project_id="project_id_value", + transaction=b'transaction_blob', + ) + + # Make the request + response = client.rollback(request=request) + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_v1.types.RollbackRequest, dict]): The request object. The request for @@ -694,7 +833,7 @@ def rollback( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([project_id, transaction]) if request is not None and has_flattened_params: @@ -739,6 +878,26 @@ def allocate_ids( r"""Allocates IDs for the given keys, which is useful for referencing an entity before it is inserted. + + .. code-block:: python + + from google.cloud import datastore_v1 + + def sample_allocate_ids(): + # Create a client + client = datastore_v1.DatastoreClient() + + # Initialize request argument(s) + request = datastore_v1.AllocateIdsRequest( + project_id="project_id_value", + ) + + # Make the request + response = client.allocate_ids(request=request) + + # Handle the response + print(response) + Args: request (Union[google.cloud.datastore_v1.types.AllocateIdsRequest, dict]): The request object. The request for @@ -772,7 +931,7 @@ def allocate_ids( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([project_id, keys]) if request is not None and has_flattened_params: @@ -814,8 +973,28 @@ def reserve_ids( timeout: float = None, metadata: Sequence[Tuple[str, str]] = (), ) -> datastore.ReserveIdsResponse: - r"""Prevents the supplied keys' IDs from being auto- - llocated by Cloud Datastore. + r"""Prevents the supplied keys' IDs from being + auto-allocated by Cloud Datastore. + + + .. code-block:: python + + from google.cloud import datastore_v1 + + def sample_reserve_ids(): + # Create a client + client = datastore_v1.DatastoreClient() + + # Initialize request argument(s) + request = datastore_v1.ReserveIdsRequest( + project_id="project_id_value", + ) + + # Make the request + response = client.reserve_ids(request=request) + + # Handle the response + print(response) Args: request (Union[google.cloud.datastore_v1.types.ReserveIdsRequest, dict]): @@ -849,7 +1028,7 @@ def reserve_ids( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([project_id, keys]) if request is not None and has_flattened_params: diff --git a/google/cloud/datastore_v1/services/datastore/transports/__init__.py b/google/cloud/datastore_v1/services/datastore/transports/__init__.py index 41074a07..b7d617f6 100644 --- a/google/cloud/datastore_v1/services/datastore/transports/__init__.py +++ b/google/cloud/datastore_v1/services/datastore/transports/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/google/cloud/datastore_v1/services/datastore/transports/base.py b/google/cloud/datastore_v1/services/datastore/transports/base.py index e23e42a2..487a1a45 100644 --- a/google/cloud/datastore_v1/services/datastore/transports/base.py +++ b/google/cloud/datastore_v1/services/datastore/transports/base.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -102,7 +102,6 @@ def __init__( credentials, _ = google.auth.load_credentials_from_file( credentials_file, **scopes_kwargs, quota_project_id=quota_project_id ) - elif credentials is None: credentials, _ = google.auth.default( **scopes_kwargs, quota_project_id=quota_project_id diff --git a/google/cloud/datastore_v1/services/datastore/transports/grpc.py b/google/cloud/datastore_v1/services/datastore/transports/grpc.py index 79071c80..410aa89d 100644 --- a/google/cloud/datastore_v1/services/datastore/transports/grpc.py +++ b/google/cloud/datastore_v1/services/datastore/transports/grpc.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore -import grpc +import grpc # type: ignore from google.cloud.datastore_v1.types import datastore from .base import DatastoreTransport, DEFAULT_CLIENT_INFO @@ -164,8 +164,11 @@ def __init__( if not self._grpc_channel: self._grpc_channel = type(self).create_channel( self._host, + # use the credentials which are saved credentials=self._credentials, - credentials_file=credentials_file, + # Set ``credentials_file`` to ``None`` here as + # the credentials that we saved earlier should be used. + credentials_file=None, scopes=self._scopes, ssl_credentials=self._ssl_channel_credentials, quota_project_id=quota_project_id, @@ -393,8 +396,8 @@ def reserve_ids( ) -> Callable[[datastore.ReserveIdsRequest], datastore.ReserveIdsResponse]: r"""Return a callable for the reserve ids method over gRPC. - Prevents the supplied keys' IDs from being auto- - llocated by Cloud Datastore. + Prevents the supplied keys' IDs from being + auto-allocated by Cloud Datastore. Returns: Callable[[~.ReserveIdsRequest], diff --git a/google/cloud/datastore_v1/services/datastore/transports/grpc_asyncio.py b/google/cloud/datastore_v1/services/datastore/transports/grpc_asyncio.py index 7543acee..f539e84f 100644 --- a/google/cloud/datastore_v1/services/datastore/transports/grpc_asyncio.py +++ b/google/cloud/datastore_v1/services/datastore/transports/grpc_asyncio.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,8 +21,8 @@ from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore -import grpc -from grpc.experimental import aio +import grpc # type: ignore +from grpc.experimental import aio # type: ignore from google.cloud.datastore_v1.types import datastore from .base import DatastoreTransport, DEFAULT_CLIENT_INFO @@ -209,8 +209,11 @@ def __init__( if not self._grpc_channel: self._grpc_channel = type(self).create_channel( self._host, + # use the credentials which are saved credentials=self._credentials, - credentials_file=credentials_file, + # Set ``credentials_file`` to ``None`` here as + # the credentials that we saved earlier should be used. + credentials_file=None, scopes=self._scopes, ssl_credentials=self._ssl_channel_credentials, quota_project_id=quota_project_id, @@ -404,8 +407,8 @@ def reserve_ids( ]: r"""Return a callable for the reserve ids method over gRPC. - Prevents the supplied keys' IDs from being auto- - llocated by Cloud Datastore. + Prevents the supplied keys' IDs from being + auto-allocated by Cloud Datastore. Returns: Callable[[~.ReserveIdsRequest], diff --git a/google/cloud/datastore_v1/types/__init__.py b/google/cloud/datastore_v1/types/__init__.py index 7553ac77..eb4fc8c2 100644 --- a/google/cloud/datastore_v1/types/__init__.py +++ b/google/cloud/datastore_v1/types/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/google/cloud/datastore_v1/types/datastore.py b/google/cloud/datastore_v1/types/datastore.py index efd696aa..e77ad1e9 100644 --- a/google/cloud/datastore_v1/types/datastore.py +++ b/google/cloud/datastore_v1/types/datastore.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -import proto +import proto # type: ignore from google.cloud.datastore_v1.types import entity from google.cloud.datastore_v1.types import query as gd_query @@ -319,8 +319,8 @@ class ReserveIdsRequest(proto.Message): which to make the request. keys (Sequence[google.cloud.datastore_v1.types.Key]): Required. A list of keys with complete key - paths whose numeric IDs should not be auto- - allocated. + paths whose numeric IDs should not be + auto-allocated. """ project_id = proto.Field(proto.STRING, number=8,) @@ -366,8 +366,8 @@ class Mutation(proto.Message): delete (google.cloud.datastore_v1.types.Key): The key of the entity to delete. The entity may or may not already exist. Must have a - complete key path and must not be reserved/read- - only. + complete key path and must not be + reserved/read-only. This field is a member of `oneof`_ ``operation``. base_version (int): diff --git a/google/cloud/datastore_v1/types/entity.py b/google/cloud/datastore_v1/types/entity.py index 8bf66889..1c432ee6 100644 --- a/google/cloud/datastore_v1/types/entity.py +++ b/google/cloud/datastore_v1/types/entity.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,11 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. # -import proto +import proto # type: ignore -from google.protobuf import struct_pb2 -from google.protobuf import timestamp_pb2 -from google.type import latlng_pb2 +from google.protobuf import struct_pb2 # type: ignore +from google.protobuf import timestamp_pb2 # type: ignore +from google.type import latlng_pb2 # type: ignore __protobuf__ = proto.module( diff --git a/google/cloud/datastore_v1/types/query.py b/google/cloud/datastore_v1/types/query.py index 6ae31257..46147f05 100644 --- a/google/cloud/datastore_v1/types/query.py +++ b/google/cloud/datastore_v1/types/query.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,10 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. # -import proto +import proto # type: ignore from google.cloud.datastore_v1.types import entity as gd_entity -from google.protobuf import wrappers_pb2 +from google.protobuf import wrappers_pb2 # type: ignore __protobuf__ = proto.module( diff --git a/scripts/fixup_datastore_admin_v1_keywords.py b/scripts/fixup_datastore_admin_v1_keywords.py index 12e217de..49b96026 100644 --- a/scripts/fixup_datastore_admin_v1_keywords.py +++ b/scripts/fixup_datastore_admin_v1_keywords.py @@ -1,6 +1,6 @@ #! /usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/scripts/fixup_datastore_v1_keywords.py b/scripts/fixup_datastore_v1_keywords.py index e0358795..4f5265b6 100644 --- a/scripts/fixup_datastore_v1_keywords.py +++ b/scripts/fixup_datastore_v1_keywords.py @@ -1,6 +1,6 @@ #! /usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/__init__.py b/tests/__init__.py index 4de65971..e8e1c384 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py index 4de65971..e8e1c384 100644 --- a/tests/unit/__init__.py +++ b/tests/unit/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/unit/gapic/__init__.py b/tests/unit/gapic/__init__.py index 4de65971..e8e1c384 100644 --- a/tests/unit/gapic/__init__.py +++ b/tests/unit/gapic/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/unit/gapic/datastore_admin_v1/__init__.py b/tests/unit/gapic/datastore_admin_v1/__init__.py index 4de65971..e8e1c384 100644 --- a/tests/unit/gapic/datastore_admin_v1/__init__.py +++ b/tests/unit/gapic/datastore_admin_v1/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/unit/gapic/datastore_admin_v1/test_datastore_admin.py b/tests/unit/gapic/datastore_admin_v1/test_datastore_admin.py index 293b1111..e6ed5508 100644 --- a/tests/unit/gapic/datastore_admin_v1/test_datastore_admin.py +++ b/tests/unit/gapic/datastore_admin_v1/test_datastore_admin.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -29,7 +29,8 @@ from google.api_core import gapic_v1 from google.api_core import grpc_helpers from google.api_core import grpc_helpers_async -from google.api_core import operation_async +from google.api_core import operation +from google.api_core import operation_async # type: ignore from google.api_core import operations_v1 from google.api_core import path_template from google.auth import credentials as ga_credentials @@ -255,20 +256,20 @@ def test_datastore_admin_client_client_options( # unsupported value. with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): with pytest.raises(MutualTLSChannelError): - client = client_class() + client = client_class(transport=transport_name) # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. with mock.patch.dict( os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} ): with pytest.raises(ValueError): - client = client_class() + client = client_class(transport=transport_name) # Check the case quota_project_id is provided options = client_options.ClientOptions(quota_project_id="octopus") with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None - client = client_class(transport=transport_name, client_options=options) + client = client_class(client_options=options, transport=transport_name) patched.assert_called_once_with( credentials=None, credentials_file=None, @@ -327,7 +328,7 @@ def test_datastore_admin_client_mtls_env_auto( ) with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None - client = client_class(transport=transport_name, client_options=options) + client = client_class(client_options=options, transport=transport_name) if use_client_cert_env == "false": expected_client_cert_source = None @@ -404,6 +405,87 @@ def test_datastore_admin_client_mtls_env_auto( ) +@pytest.mark.parametrize( + "client_class", [DatastoreAdminClient, DatastoreAdminAsyncClient] +) +@mock.patch.object( + DatastoreAdminClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(DatastoreAdminClient), +) +@mock.patch.object( + DatastoreAdminAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(DatastoreAdminAsyncClient), +) +def test_datastore_admin_client_get_mtls_endpoint_and_cert_source(client_class): + mock_client_cert_source = mock.Mock() + + # Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "true". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + mock_api_endpoint = "foo" + options = client_options.ClientOptions( + client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint + ) + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source( + options + ) + assert api_endpoint == mock_api_endpoint + assert cert_source == mock_client_cert_source + + # Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "false". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): + mock_client_cert_source = mock.Mock() + mock_api_endpoint = "foo" + options = client_options.ClientOptions( + client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint + ) + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source( + options + ) + assert api_endpoint == mock_api_endpoint + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "never". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_ENDPOINT + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "always". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert doesn't exist. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_ENDPOINT + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert exists. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, + ): + with mock.patch( + "google.auth.transport.mtls.default_client_cert_source", + return_value=mock_client_cert_source, + ): + ( + api_endpoint, + cert_source, + ) = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT + assert cert_source == mock_client_cert_source + + @pytest.mark.parametrize( "client_class,transport_class,transport_name", [ @@ -422,7 +504,7 @@ def test_datastore_admin_client_client_options_scopes( options = client_options.ClientOptions(scopes=["1", "2"],) with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None - client = client_class(transport=transport_name, client_options=options) + client = client_class(client_options=options, transport=transport_name) patched.assert_called_once_with( credentials=None, credentials_file=None, @@ -436,24 +518,31 @@ def test_datastore_admin_client_client_options_scopes( @pytest.mark.parametrize( - "client_class,transport_class,transport_name", + "client_class,transport_class,transport_name,grpc_helpers", [ - (DatastoreAdminClient, transports.DatastoreAdminGrpcTransport, "grpc"), + ( + DatastoreAdminClient, + transports.DatastoreAdminGrpcTransport, + "grpc", + grpc_helpers, + ), ( DatastoreAdminAsyncClient, transports.DatastoreAdminGrpcAsyncIOTransport, "grpc_asyncio", + grpc_helpers_async, ), ], ) def test_datastore_admin_client_client_options_credentials_file( - client_class, transport_class, transport_name + client_class, transport_class, transport_name, grpc_helpers ): # Check the case credentials file is provided. options = client_options.ClientOptions(credentials_file="credentials.json") + with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None - client = client_class(transport=transport_name, client_options=options) + client = client_class(client_options=options, transport=transport_name) patched.assert_called_once_with( credentials=None, credentials_file="credentials.json", @@ -486,9 +575,77 @@ def test_datastore_admin_client_client_options_from_dict(): ) -def test_export_entities( - transport: str = "grpc", request_type=datastore_admin.ExportEntitiesRequest +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,grpc_helpers", + [ + ( + DatastoreAdminClient, + transports.DatastoreAdminGrpcTransport, + "grpc", + grpc_helpers, + ), + ( + DatastoreAdminAsyncClient, + transports.DatastoreAdminGrpcAsyncIOTransport, + "grpc_asyncio", + grpc_helpers_async, + ), + ], +) +def test_datastore_admin_client_create_channel_credentials_file( + client_class, transport_class, transport_name, grpc_helpers ): + # Check the case credentials file is provided. + options = client_options.ClientOptions(credentials_file="credentials.json") + + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options, transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file="credentials.json", + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + ) + + # test that the credentials from file are saved and used as the credentials. + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch.object( + google.auth, "default", autospec=True + ) as adc, mock.patch.object( + grpc_helpers, "create_channel" + ) as create_channel: + creds = ga_credentials.AnonymousCredentials() + file_creds = ga_credentials.AnonymousCredentials() + load_creds.return_value = (file_creds, None) + adc.return_value = (creds, None) + client = client_class(client_options=options, transport=transport_name) + create_channel.assert_called_with( + "datastore.googleapis.com:443", + credentials=file_creds, + credentials_file=None, + quota_project_id=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/datastore", + ), + scopes=None, + default_host="datastore.googleapis.com", + ssl_credentials=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + +@pytest.mark.parametrize("request_type", [datastore_admin.ExportEntitiesRequest, dict,]) +def test_export_entities(request_type, transport: str = "grpc"): client = DatastoreAdminClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -512,10 +669,6 @@ def test_export_entities( assert isinstance(response, future.Future) -def test_export_entities_from_dict(): - test_export_entities(request_type=dict) - - def test_export_entities_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -673,9 +826,8 @@ async def test_export_entities_flattened_error_async(): ) -def test_import_entities( - transport: str = "grpc", request_type=datastore_admin.ImportEntitiesRequest -): +@pytest.mark.parametrize("request_type", [datastore_admin.ImportEntitiesRequest, dict,]) +def test_import_entities(request_type, transport: str = "grpc"): client = DatastoreAdminClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -699,10 +851,6 @@ def test_import_entities( assert isinstance(response, future.Future) -def test_import_entities_from_dict(): - test_import_entities(request_type=dict) - - def test_import_entities_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -860,9 +1008,8 @@ async def test_import_entities_flattened_error_async(): ) -def test_create_index( - transport: str = "grpc", request_type=datastore_admin.CreateIndexRequest -): +@pytest.mark.parametrize("request_type", [datastore_admin.CreateIndexRequest, dict,]) +def test_create_index(request_type, transport: str = "grpc"): client = DatastoreAdminClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -886,10 +1033,6 @@ def test_create_index( assert isinstance(response, future.Future) -def test_create_index_from_dict(): - test_create_index(request_type=dict) - - def test_create_index_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -939,9 +1082,8 @@ async def test_create_index_async_from_dict(): await test_create_index_async(request_type=dict) -def test_delete_index( - transport: str = "grpc", request_type=datastore_admin.DeleteIndexRequest -): +@pytest.mark.parametrize("request_type", [datastore_admin.DeleteIndexRequest, dict,]) +def test_delete_index(request_type, transport: str = "grpc"): client = DatastoreAdminClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -965,10 +1107,6 @@ def test_delete_index( assert isinstance(response, future.Future) -def test_delete_index_from_dict(): - test_delete_index(request_type=dict) - - def test_delete_index_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -1018,9 +1156,8 @@ async def test_delete_index_async_from_dict(): await test_delete_index_async(request_type=dict) -def test_get_index( - transport: str = "grpc", request_type=datastore_admin.GetIndexRequest -): +@pytest.mark.parametrize("request_type", [datastore_admin.GetIndexRequest, dict,]) +def test_get_index(request_type, transport: str = "grpc"): client = DatastoreAdminClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -1055,10 +1192,6 @@ def test_get_index( assert response.state == index.Index.State.CREATING -def test_get_index_from_dict(): - test_get_index(request_type=dict) - - def test_get_index_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -1119,9 +1252,8 @@ async def test_get_index_async_from_dict(): await test_get_index_async(request_type=dict) -def test_list_indexes( - transport: str = "grpc", request_type=datastore_admin.ListIndexesRequest -): +@pytest.mark.parametrize("request_type", [datastore_admin.ListIndexesRequest, dict,]) +def test_list_indexes(request_type, transport: str = "grpc"): client = DatastoreAdminClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -1148,10 +1280,6 @@ def test_list_indexes( assert response.next_page_token == "next_page_token_value" -def test_list_indexes_from_dict(): - test_list_indexes(request_type=dict) - - def test_list_indexes_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -1204,8 +1332,10 @@ async def test_list_indexes_async_from_dict(): await test_list_indexes_async(request_type=dict) -def test_list_indexes_pager(): - client = DatastoreAdminClient(credentials=ga_credentials.AnonymousCredentials,) +def test_list_indexes_pager(transport_name: str = "grpc"): + client = DatastoreAdminClient( + credentials=ga_credentials.AnonymousCredentials, transport=transport_name, + ) # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object(type(client.transport.list_indexes), "__call__") as call: @@ -1235,8 +1365,10 @@ def test_list_indexes_pager(): assert all(isinstance(i, index.Index) for i in results) -def test_list_indexes_pages(): - client = DatastoreAdminClient(credentials=ga_credentials.AnonymousCredentials,) +def test_list_indexes_pages(transport_name: str = "grpc"): + client = DatastoreAdminClient( + credentials=ga_credentials.AnonymousCredentials, transport=transport_name, + ) # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object(type(client.transport.list_indexes), "__call__") as call: @@ -1343,6 +1475,23 @@ def test_credentials_transport_error(): transport=transport, ) + # It is an error to provide an api_key and a transport instance. + transport = transports.DatastoreAdminGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = DatastoreAdminClient(client_options=options, transport=transport,) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = DatastoreAdminClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + # It is an error to provide scopes and a transport instance. transport = transports.DatastoreAdminGrpcTransport( credentials=ga_credentials.AnonymousCredentials(), @@ -1861,7 +2010,7 @@ def test_parse_common_location_path(): assert expected == actual -def test_client_withDEFAULT_CLIENT_INFO(): +def test_client_with_default_client_info(): client_info = gapic_v1.client_info.ClientInfo() with mock.patch.object( @@ -1926,3 +2075,33 @@ def test_client_ctx(): with client: pass close.assert_called() + + +@pytest.mark.parametrize( + "client_class,transport_class", + [ + (DatastoreAdminClient, transports.DatastoreAdminGrpcTransport), + (DatastoreAdminAsyncClient, transports.DatastoreAdminGrpcAsyncIOTransport), + ], +) +def test_api_key_credentials(client_class, transport_class): + with mock.patch.object( + google.auth._default, "get_api_key_credentials", create=True + ) as get_api_key_credentials: + mock_cred = mock.Mock() + get_api_key_credentials.return_value = mock_cred + options = client_options.ClientOptions() + options.api_key = "api_key" + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=mock_cred, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + ) diff --git a/tests/unit/gapic/datastore_v1/__init__.py b/tests/unit/gapic/datastore_v1/__init__.py index 4de65971..e8e1c384 100644 --- a/tests/unit/gapic/datastore_v1/__init__.py +++ b/tests/unit/gapic/datastore_v1/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/unit/gapic/datastore_v1/test_datastore.py b/tests/unit/gapic/datastore_v1/test_datastore.py index acfffd84..fee5a408 100644 --- a/tests/unit/gapic/datastore_v1/test_datastore.py +++ b/tests/unit/gapic/datastore_v1/test_datastore.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -38,10 +38,10 @@ from google.cloud.datastore_v1.types import entity from google.cloud.datastore_v1.types import query from google.oauth2 import service_account -from google.protobuf import struct_pb2 -from google.protobuf import timestamp_pb2 -from google.protobuf import wrappers_pb2 -from google.type import latlng_pb2 +from google.protobuf import struct_pb2 # type: ignore +from google.protobuf import timestamp_pb2 # type: ignore +from google.protobuf import wrappers_pb2 # type: ignore +from google.type import latlng_pb2 # type: ignore import google.auth @@ -238,20 +238,20 @@ def test_datastore_client_client_options(client_class, transport_class, transpor # unsupported value. with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): with pytest.raises(MutualTLSChannelError): - client = client_class() + client = client_class(transport=transport_name) # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. with mock.patch.dict( os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} ): with pytest.raises(ValueError): - client = client_class() + client = client_class(transport=transport_name) # Check the case quota_project_id is provided options = client_options.ClientOptions(quota_project_id="octopus") with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None - client = client_class(transport=transport_name, client_options=options) + client = client_class(client_options=options, transport=transport_name) patched.assert_called_once_with( credentials=None, credentials_file=None, @@ -308,7 +308,7 @@ def test_datastore_client_mtls_env_auto( ) with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None - client = client_class(transport=transport_name, client_options=options) + client = client_class(client_options=options, transport=transport_name) if use_client_cert_env == "false": expected_client_cert_source = None @@ -385,6 +385,83 @@ def test_datastore_client_mtls_env_auto( ) +@pytest.mark.parametrize("client_class", [DatastoreClient, DatastoreAsyncClient]) +@mock.patch.object( + DatastoreClient, "DEFAULT_ENDPOINT", modify_default_endpoint(DatastoreClient) +) +@mock.patch.object( + DatastoreAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(DatastoreAsyncClient), +) +def test_datastore_client_get_mtls_endpoint_and_cert_source(client_class): + mock_client_cert_source = mock.Mock() + + # Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "true". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + mock_api_endpoint = "foo" + options = client_options.ClientOptions( + client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint + ) + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source( + options + ) + assert api_endpoint == mock_api_endpoint + assert cert_source == mock_client_cert_source + + # Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "false". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): + mock_client_cert_source = mock.Mock() + mock_api_endpoint = "foo" + options = client_options.ClientOptions( + client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint + ) + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source( + options + ) + assert api_endpoint == mock_api_endpoint + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "never". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_ENDPOINT + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "always". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert doesn't exist. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_ENDPOINT + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert exists. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, + ): + with mock.patch( + "google.auth.transport.mtls.default_client_cert_source", + return_value=mock_client_cert_source, + ): + ( + api_endpoint, + cert_source, + ) = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT + assert cert_source == mock_client_cert_source + + @pytest.mark.parametrize( "client_class,transport_class,transport_name", [ @@ -403,7 +480,7 @@ def test_datastore_client_client_options_scopes( options = client_options.ClientOptions(scopes=["1", "2"],) with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None - client = client_class(transport=transport_name, client_options=options) + client = client_class(client_options=options, transport=transport_name) patched.assert_called_once_with( credentials=None, credentials_file=None, @@ -417,24 +494,26 @@ def test_datastore_client_client_options_scopes( @pytest.mark.parametrize( - "client_class,transport_class,transport_name", + "client_class,transport_class,transport_name,grpc_helpers", [ - (DatastoreClient, transports.DatastoreGrpcTransport, "grpc"), + (DatastoreClient, transports.DatastoreGrpcTransport, "grpc", grpc_helpers), ( DatastoreAsyncClient, transports.DatastoreGrpcAsyncIOTransport, "grpc_asyncio", + grpc_helpers_async, ), ], ) def test_datastore_client_client_options_credentials_file( - client_class, transport_class, transport_name + client_class, transport_class, transport_name, grpc_helpers ): # Check the case credentials file is provided. options = client_options.ClientOptions(credentials_file="credentials.json") + with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None - client = client_class(transport=transport_name, client_options=options) + client = client_class(client_options=options, transport=transport_name) patched.assert_called_once_with( credentials=None, credentials_file="credentials.json", @@ -465,7 +544,72 @@ def test_datastore_client_client_options_from_dict(): ) -def test_lookup(transport: str = "grpc", request_type=datastore.LookupRequest): +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,grpc_helpers", + [ + (DatastoreClient, transports.DatastoreGrpcTransport, "grpc", grpc_helpers), + ( + DatastoreAsyncClient, + transports.DatastoreGrpcAsyncIOTransport, + "grpc_asyncio", + grpc_helpers_async, + ), + ], +) +def test_datastore_client_create_channel_credentials_file( + client_class, transport_class, transport_name, grpc_helpers +): + # Check the case credentials file is provided. + options = client_options.ClientOptions(credentials_file="credentials.json") + + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options, transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file="credentials.json", + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + ) + + # test that the credentials from file are saved and used as the credentials. + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch.object( + google.auth, "default", autospec=True + ) as adc, mock.patch.object( + grpc_helpers, "create_channel" + ) as create_channel: + creds = ga_credentials.AnonymousCredentials() + file_creds = ga_credentials.AnonymousCredentials() + load_creds.return_value = (file_creds, None) + adc.return_value = (creds, None) + client = client_class(client_options=options, transport=transport_name) + create_channel.assert_called_with( + "datastore.googleapis.com:443", + credentials=file_creds, + credentials_file=None, + quota_project_id=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/datastore", + ), + scopes=None, + default_host="datastore.googleapis.com", + ssl_credentials=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + +@pytest.mark.parametrize("request_type", [datastore.LookupRequest, dict,]) +def test_lookup(request_type, transport: str = "grpc"): client = DatastoreClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -489,10 +633,6 @@ def test_lookup(transport: str = "grpc", request_type=datastore.LookupRequest): assert isinstance(response, datastore.LookupResponse) -def test_lookup_from_dict(): - test_lookup(request_type=dict) - - def test_lookup_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -668,7 +808,8 @@ async def test_lookup_flattened_error_async(): ) -def test_run_query(transport: str = "grpc", request_type=datastore.RunQueryRequest): +@pytest.mark.parametrize("request_type", [datastore.RunQueryRequest, dict,]) +def test_run_query(request_type, transport: str = "grpc"): client = DatastoreClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -692,10 +833,6 @@ def test_run_query(transport: str = "grpc", request_type=datastore.RunQueryReque assert isinstance(response, datastore.RunQueryResponse) -def test_run_query_from_dict(): - test_run_query(request_type=dict) - - def test_run_query_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -745,9 +882,8 @@ async def test_run_query_async_from_dict(): await test_run_query_async(request_type=dict) -def test_begin_transaction( - transport: str = "grpc", request_type=datastore.BeginTransactionRequest -): +@pytest.mark.parametrize("request_type", [datastore.BeginTransactionRequest, dict,]) +def test_begin_transaction(request_type, transport: str = "grpc"): client = DatastoreClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -776,10 +912,6 @@ def test_begin_transaction( assert response.transaction == b"transaction_blob" -def test_begin_transaction_from_dict(): - test_begin_transaction(request_type=dict) - - def test_begin_transaction_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -906,7 +1038,8 @@ async def test_begin_transaction_flattened_error_async(): ) -def test_commit(transport: str = "grpc", request_type=datastore.CommitRequest): +@pytest.mark.parametrize("request_type", [datastore.CommitRequest, dict,]) +def test_commit(request_type, transport: str = "grpc"): client = DatastoreClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -931,10 +1064,6 @@ def test_commit(transport: str = "grpc", request_type=datastore.CommitRequest): assert response.index_updates == 1389 -def test_commit_from_dict(): - test_commit(request_type=dict) - - def test_commit_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -1141,7 +1270,8 @@ async def test_commit_flattened_error_async(): ) -def test_rollback(transport: str = "grpc", request_type=datastore.RollbackRequest): +@pytest.mark.parametrize("request_type", [datastore.RollbackRequest, dict,]) +def test_rollback(request_type, transport: str = "grpc"): client = DatastoreClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -1165,10 +1295,6 @@ def test_rollback(transport: str = "grpc", request_type=datastore.RollbackReques assert isinstance(response, datastore.RollbackResponse) -def test_rollback_from_dict(): - test_rollback(request_type=dict) - - def test_rollback_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -1300,9 +1426,8 @@ async def test_rollback_flattened_error_async(): ) -def test_allocate_ids( - transport: str = "grpc", request_type=datastore.AllocateIdsRequest -): +@pytest.mark.parametrize("request_type", [datastore.AllocateIdsRequest, dict,]) +def test_allocate_ids(request_type, transport: str = "grpc"): client = DatastoreClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -1326,10 +1451,6 @@ def test_allocate_ids( assert isinstance(response, datastore.AllocateIdsResponse) -def test_allocate_ids_from_dict(): - test_allocate_ids(request_type=dict) - - def test_allocate_ids_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -1483,7 +1604,8 @@ async def test_allocate_ids_flattened_error_async(): ) -def test_reserve_ids(transport: str = "grpc", request_type=datastore.ReserveIdsRequest): +@pytest.mark.parametrize("request_type", [datastore.ReserveIdsRequest, dict,]) +def test_reserve_ids(request_type, transport: str = "grpc"): client = DatastoreClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -1507,10 +1629,6 @@ def test_reserve_ids(transport: str = "grpc", request_type=datastore.ReserveIdsR assert isinstance(response, datastore.ReserveIdsResponse) -def test_reserve_ids_from_dict(): - test_reserve_ids(request_type=dict) - - def test_reserve_ids_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -1684,6 +1802,23 @@ def test_credentials_transport_error(): transport=transport, ) + # It is an error to provide an api_key and a transport instance. + transport = transports.DatastoreGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = DatastoreClient(client_options=options, transport=transport,) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = DatastoreClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + # It is an error to provide scopes and a transport instance. transport = transports.DatastoreGrpcTransport( credentials=ga_credentials.AnonymousCredentials(), @@ -2155,7 +2290,7 @@ def test_parse_common_location_path(): assert expected == actual -def test_client_withDEFAULT_CLIENT_INFO(): +def test_client_with_default_client_info(): client_info = gapic_v1.client_info.ClientInfo() with mock.patch.object( @@ -2220,3 +2355,33 @@ def test_client_ctx(): with client: pass close.assert_called() + + +@pytest.mark.parametrize( + "client_class,transport_class", + [ + (DatastoreClient, transports.DatastoreGrpcTransport), + (DatastoreAsyncClient, transports.DatastoreGrpcAsyncIOTransport), + ], +) +def test_api_key_credentials(client_class, transport_class): + with mock.patch.object( + google.auth._default, "get_api_key_credentials", create=True + ) as get_api_key_credentials: + mock_cred = mock.Mock() + get_api_key_credentials.return_value = mock_cred + options = client_options.ClientOptions() + options.api_key = "api_key" + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=mock_cred, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + )