diff --git a/.librarian/state.yaml b/.librarian/state.yaml index bbd03fcacef7..848b49f3e95e 100644 --- a/.librarian/state.yaml +++ b/.librarian/state.yaml @@ -11,6 +11,7 @@ # 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. + image: us-central1-docker.pkg.dev/cloud-sdk-librarian-prod/images-prod/python-librarian-generator@sha256:234b9d1f2ddb057ed7ac6a38db0bf8163d839c65c6cf88ade52530cddebce59e libraries: - id: bigframes @@ -177,6 +178,7 @@ libraries: last_generated_commit: 7a5706618f42f482acf583febcc7b977b66c25b2 apis: - path: google/apps/card/v1 + service_config: "" source_roots: - packages/google-apps-card preserve_regex: @@ -237,12 +239,19 @@ libraries: last_generated_commit: 3322511885371d2b2253f209ccc3aa60d4100cfd apis: - path: google/apps/script/type + service_config: "" - path: google/apps/script/type/gmail + service_config: "" - path: google/apps/script/type/docs + service_config: "" - path: google/apps/script/type/drive + service_config: "" - path: google/apps/script/type/sheets + service_config: "" - path: google/apps/script/type/calendar + service_config: "" - path: google/apps/script/type/slides + service_config: "" source_roots: - packages/google-apps-script-type preserve_regex: @@ -324,6 +333,7 @@ libraries: - path: google/identity/accesscontextmanager/v1 service_config: accesscontextmanager_v1.yaml - path: google/identity/accesscontextmanager/type + service_config: "" source_roots: - packages/google-cloud-access-context-manager preserve_regex: [] @@ -491,6 +501,7 @@ libraries: last_generated_commit: 3322511885371d2b2253f209ccc3aa60d4100cfd apis: - path: google/appengine/logging/v1 + service_config: "" source_roots: - packages/google-cloud-appengine-logging preserve_regex: @@ -893,6 +904,7 @@ libraries: last_generated_commit: 3322511885371d2b2253f209ccc3aa60d4100cfd apis: - path: google/cloud/bigquery/logging/v1 + service_config: "" source_roots: - packages/google-cloud-bigquery-logging preserve_regex: @@ -1954,6 +1966,7 @@ libraries: - path: google/firestore/admin/v1 service_config: firestore_v1.yaml - path: google/firestore/bundle + service_config: "" - path: google/firestore/v1 service_config: firestore_v1.yaml source_roots: @@ -2193,6 +2206,7 @@ libraries: last_generated_commit: 3322511885371d2b2253f209ccc3aa60d4100cfd apis: - path: google/iam/v1/logging + service_config: "" source_roots: - packages/google-cloud-iam-logging preserve_regex: @@ -2734,6 +2748,7 @@ libraries: last_generated_commit: 55319b058f8a0e46bbeeff30e374e4b1f081f494 apis: - path: google/cloud/orgpolicy/v1 + service_config: "" - path: google/cloud/orgpolicy/v2 service_config: orgpolicy_v2.yaml source_roots: @@ -3313,6 +3328,7 @@ libraries: last_generated_commit: 3322511885371d2b2253f209ccc3aa60d4100cfd apis: - path: google/devtools/source/v1 + service_config: "" source_roots: - packages/google-cloud-source-context preserve_regex: @@ -3669,7 +3685,7 @@ libraries: tag_format: '{id}-v{version}' - id: google-cloud-vectorsearch version: 0.9.0 - last_generated_commit: ebfdba37e54d9cd3e78380d226c2c4ab5a5f7fd4 + last_generated_commit: 38ed7d6ba66a774924722146f054d12b4487a89f apis: - path: google/cloud/vectorsearch/v1beta service_config: vectorsearch_v1beta.yaml @@ -4356,6 +4372,7 @@ libraries: last_generated_commit: 6df3ecf4fd43b64826de6a477d1a535ec18b0d7c apis: - path: google/shopping/type + service_config: "" source_roots: - packages/google-shopping-type preserve_regex: @@ -4372,12 +4389,15 @@ libraries: - path: google/api service_config: serviceconfig.yaml - path: google/cloud + service_config: "" - path: google/cloud/location service_config: cloud.yaml - path: google/logging/type + service_config: "" - path: google/rpc service_config: rpc_publish.yaml - path: google/rpc/context + service_config: "" - path: google/type service_config: type.yaml source_roots: diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch/__init__.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch/__init__.py index ef9a548e91cf..be81969b6e7d 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch/__init__.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch/__init__.py @@ -78,6 +78,7 @@ EmbeddingTaskType, VertexEmbeddingConfig, ) +from google.cloud.vectorsearch_v1.types.encryption_spec import EncryptionSpec from google.cloud.vectorsearch_v1.types.vectorsearch_service import ( Collection, CreateCollectionRequest, @@ -103,6 +104,7 @@ OperationMetadata, SparseVectorField, UpdateCollectionRequest, + UpdateIndexRequest, VectorField, ) @@ -147,6 +149,7 @@ "UpdateDataObjectRequest", "VertexEmbeddingConfig", "EmbeddingTaskType", + "EncryptionSpec", "Collection", "CreateCollectionRequest", "CreateIndexRequest", @@ -171,5 +174,6 @@ "OperationMetadata", "SparseVectorField", "UpdateCollectionRequest", + "UpdateIndexRequest", "VectorField", ) diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/__init__.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/__init__.py index 0646aeda35ff..0832eaa9b4b9 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/__init__.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/__init__.py @@ -75,6 +75,7 @@ UpdateDataObjectRequest, ) from .types.embedding_config import EmbeddingTaskType, VertexEmbeddingConfig +from .types.encryption_spec import EncryptionSpec from .types.vectorsearch_service import ( Collection, CreateCollectionRequest, @@ -100,6 +101,7 @@ OperationMetadata, SparseVectorField, UpdateCollectionRequest, + UpdateIndexRequest, VectorField, ) @@ -227,6 +229,7 @@ def _get_version(dependency_name): "DenseVectorField", "DistanceMetric", "EmbeddingTaskType", + "EncryptionSpec", "ExportDataObjectsMetadata", "ExportDataObjectsRequest", "ExportDataObjectsResponse", @@ -258,6 +261,7 @@ def _get_version(dependency_name): "TextSearch", "UpdateCollectionRequest", "UpdateDataObjectRequest", + "UpdateIndexRequest", "Vector", "VectorField", "VectorSearch", diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/gapic_metadata.json b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/gapic_metadata.json index 40efa809326e..78901777ab0a 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/gapic_metadata.json +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/gapic_metadata.json @@ -267,6 +267,11 @@ "methods": [ "update_collection" ] + }, + "UpdateIndex": { + "methods": [ + "update_index" + ] } } }, @@ -327,6 +332,11 @@ "methods": [ "update_collection" ] + }, + "UpdateIndex": { + "methods": [ + "update_index" + ] } } }, @@ -387,6 +397,11 @@ "methods": [ "update_collection" ] + }, + "UpdateIndex": { + "methods": [ + "update_index" + ] } } } diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/async_client.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/async_client.py index fe141589c499..266b9367e237 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/async_client.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/async_client.py @@ -54,7 +54,11 @@ from google.longrunning import operations_pb2 # type: ignore from google.cloud.vectorsearch_v1.services.vector_search_service import pagers -from google.cloud.vectorsearch_v1.types import common, vectorsearch_service +from google.cloud.vectorsearch_v1.types import ( + common, + encryption_spec, + vectorsearch_service, +) from .client import VectorSearchServiceClient from .transports.base import DEFAULT_CLIENT_INFO, VectorSearchServiceTransport @@ -92,6 +96,10 @@ class VectorSearchServiceAsyncClient: parse_collection_path = staticmethod( VectorSearchServiceClient.parse_collection_path ) + crypto_key_path = staticmethod(VectorSearchServiceClient.crypto_key_path) + parse_crypto_key_path = staticmethod( + VectorSearchServiceClient.parse_crypto_key_path + ) index_path = staticmethod(VectorSearchServiceClient.index_path) parse_index_path = staticmethod(VectorSearchServiceClient.parse_index_path) common_billing_account_path = staticmethod( @@ -1375,6 +1383,160 @@ async def sample_create_index(): # Done; return the response. return response + async def update_index( + self, + request: Optional[Union[vectorsearch_service.UpdateIndexRequest, dict]] = None, + *, + index: Optional[vectorsearch_service.Index] = None, + update_mask: Optional[field_mask_pb2.FieldMask] = None, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> operation_async.AsyncOperation: + r"""Updates the parameters of a single Index. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import vectorsearch_v1 + + async def sample_update_index(): + # Create a client + client = vectorsearch_v1.VectorSearchServiceAsyncClient() + + # Initialize request argument(s) + index = vectorsearch_v1.Index() + index.index_field = "index_field_value" + + request = vectorsearch_v1.UpdateIndexRequest( + index=index, + ) + + # Make the request + operation = client.update_index(request=request) + + print("Waiting for operation to complete...") + + response = (await operation).result() + + # Handle the response + print(response) + + Args: + request (Optional[Union[google.cloud.vectorsearch_v1.types.UpdateIndexRequest, dict]]): + The request object. Message for updating an Index. + index (:class:`google.cloud.vectorsearch_v1.types.Index`): + Required. The resource being updated. + This corresponds to the ``index`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + update_mask (:class:`google.protobuf.field_mask_pb2.FieldMask`): + Optional. Specifies the fields to be overwritten in the + Index resource by the update. The fields specified in + the update_mask are relative to the resource, not the + full request. A field will be overwritten if it is in + the mask. If the user does not provide a mask then all + fields present in the request with non-empty values will + be overwritten. + + The following fields support update: + + - ``display_name`` + - ``description`` + - ``labels`` + - ``dedicated_infrastructure.autoscaling_spec.min_replica_count`` + - ``dedicated_infrastructure.autoscaling_spec.max_replica_count`` + + If ``*`` is provided in the ``update_mask``, full + replacement of mutable fields will be performed. + + This corresponds to the ``update_mask`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be + sent along with the request as metadata. Normally, each value must be of type `str`, + but for metadata keys ending with the suffix `-bin`, the corresponding values must + be of type `bytes`. + + Returns: + google.api_core.operation_async.AsyncOperation: + An object representing a long-running operation. + + The result type for the operation will be + :class:`google.cloud.vectorsearch_v1.types.Index` + Message describing Index object + + """ + # Create or coerce a protobuf request object. + # - Quick check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + flattened_params = [index, update_mask] + has_flattened_params = ( + len([param for param in flattened_params if param is not None]) > 0 + ) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # - Use the request object if provided (there's no risk of modifying the input as + # there are no flattened fields), or create one. + if not isinstance(request, vectorsearch_service.UpdateIndexRequest): + request = vectorsearch_service.UpdateIndexRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + if index is not None: + request.index = index + if update_mask is not None: + request.update_mask = update_mask + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._client._transport._wrapped_methods[ + self._client._transport.update_index + ] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata( + (("index.name", request.index.name),) + ), + ) + + # Validate the universe domain. + self._client._validate_universe_domain() + + # Send the request. + response = await rpc( + request, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Wrap the response in an operation future. + response = operation_async.from_gapic( + response, + self._client._transport.operations_client, + vectorsearch_service.Index, + metadata_type=vectorsearch_service.OperationMetadata, + ) + + # Done; return the response. + return response + async def delete_index( self, request: Optional[Union[vectorsearch_service.DeleteIndexRequest, dict]] = None, diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/client.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/client.py index 20913f508876..1f9db76072de 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/client.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/client.py @@ -71,7 +71,11 @@ from google.longrunning import operations_pb2 # type: ignore from google.cloud.vectorsearch_v1.services.vector_search_service import pagers -from google.cloud.vectorsearch_v1.types import common, vectorsearch_service +from google.cloud.vectorsearch_v1.types import ( + common, + encryption_spec, + vectorsearch_service, +) from .transports.base import DEFAULT_CLIENT_INFO, VectorSearchServiceTransport from .transports.grpc import VectorSearchServiceGrpcTransport @@ -265,6 +269,30 @@ def parse_collection_path(path: str) -> Dict[str, str]: ) return m.groupdict() if m else {} + @staticmethod + def crypto_key_path( + project: str, + location: str, + key_ring: str, + crypto_key: str, + ) -> str: + """Returns a fully-qualified crypto_key string.""" + return "projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}".format( + project=project, + location=location, + key_ring=key_ring, + crypto_key=crypto_key, + ) + + @staticmethod + def parse_crypto_key_path(path: str) -> Dict[str, str]: + """Parses a crypto_key path into its component segments.""" + m = re.match( + r"^projects/(?P.+?)/locations/(?P.+?)/keyRings/(?P.+?)/cryptoKeys/(?P.+?)$", + path, + ) + return m.groupdict() if m else {} + @staticmethod def index_path( project: str, @@ -1806,6 +1834,157 @@ def sample_create_index(): # Done; return the response. return response + def update_index( + self, + request: Optional[Union[vectorsearch_service.UpdateIndexRequest, dict]] = None, + *, + index: Optional[vectorsearch_service.Index] = None, + update_mask: Optional[field_mask_pb2.FieldMask] = None, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> operation.Operation: + r"""Updates the parameters of a single Index. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import vectorsearch_v1 + + def sample_update_index(): + # Create a client + client = vectorsearch_v1.VectorSearchServiceClient() + + # Initialize request argument(s) + index = vectorsearch_v1.Index() + index.index_field = "index_field_value" + + request = vectorsearch_v1.UpdateIndexRequest( + index=index, + ) + + # Make the request + operation = client.update_index(request=request) + + print("Waiting for operation to complete...") + + response = operation.result() + + # Handle the response + print(response) + + Args: + request (Union[google.cloud.vectorsearch_v1.types.UpdateIndexRequest, dict]): + The request object. Message for updating an Index. + index (google.cloud.vectorsearch_v1.types.Index): + Required. The resource being updated. + This corresponds to the ``index`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + update_mask (google.protobuf.field_mask_pb2.FieldMask): + Optional. Specifies the fields to be overwritten in the + Index resource by the update. The fields specified in + the update_mask are relative to the resource, not the + full request. A field will be overwritten if it is in + the mask. If the user does not provide a mask then all + fields present in the request with non-empty values will + be overwritten. + + The following fields support update: + + - ``display_name`` + - ``description`` + - ``labels`` + - ``dedicated_infrastructure.autoscaling_spec.min_replica_count`` + - ``dedicated_infrastructure.autoscaling_spec.max_replica_count`` + + If ``*`` is provided in the ``update_mask``, full + replacement of mutable fields will be performed. + + This corresponds to the ``update_mask`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be + sent along with the request as metadata. Normally, each value must be of type `str`, + but for metadata keys ending with the suffix `-bin`, the corresponding values must + be of type `bytes`. + + Returns: + google.api_core.operation.Operation: + An object representing a long-running operation. + + The result type for the operation will be + :class:`google.cloud.vectorsearch_v1.types.Index` + Message describing Index object + + """ + # Create or coerce a protobuf request object. + # - Quick check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + flattened_params = [index, update_mask] + has_flattened_params = ( + len([param for param in flattened_params if param is not None]) > 0 + ) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # - Use the request object if provided (there's no risk of modifying the input as + # there are no flattened fields), or create one. + if not isinstance(request, vectorsearch_service.UpdateIndexRequest): + request = vectorsearch_service.UpdateIndexRequest(request) + # If we have keyword arguments corresponding to fields on the + # request, apply these. + if index is not None: + request.index = index + if update_mask is not None: + request.update_mask = update_mask + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.update_index] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata( + (("index.name", request.index.name),) + ), + ) + + # Validate the universe domain. + self._validate_universe_domain() + + # Send the request. + response = rpc( + request, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Wrap the response in an operation future. + response = operation.from_gapic( + response, + self._transport.operations_client, + vectorsearch_service.Index, + metadata_type=vectorsearch_service.OperationMetadata, + ) + + # Done; return the response. + return response + def delete_index( self, request: Optional[Union[vectorsearch_service.DeleteIndexRequest, dict]] = None, diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/base.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/base.py index b2f03727871e..9c77a95df2ce 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/base.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/base.py @@ -256,6 +256,11 @@ def _prep_wrapped_messages(self, client_info): default_timeout=60.0, client_info=client_info, ), + self.update_index: gapic_v1.method.wrap_method( + self.update_index, + default_timeout=None, + client_info=client_info, + ), self.delete_index: gapic_v1.method.wrap_method( self.delete_index, default_retry=retries.Retry( @@ -415,6 +420,15 @@ def create_index( ]: raise NotImplementedError() + @property + def update_index( + self, + ) -> Callable[ + [vectorsearch_service.UpdateIndexRequest], + Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]], + ]: + raise NotImplementedError() + @property def delete_index( self, diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/grpc.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/grpc.py index 914f9b94427a..b0573504999a 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/grpc.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/grpc.py @@ -572,6 +572,32 @@ def create_index( ) return self._stubs["create_index"] + @property + def update_index( + self, + ) -> Callable[[vectorsearch_service.UpdateIndexRequest], operations_pb2.Operation]: + r"""Return a callable for the update index method over gRPC. + + Updates the parameters of a single Index. + + Returns: + Callable[[~.UpdateIndexRequest], + ~.Operation]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "update_index" not in self._stubs: + self._stubs["update_index"] = self._logged_channel.unary_unary( + "/google.cloud.vectorsearch.v1.VectorSearchService/UpdateIndex", + request_serializer=vectorsearch_service.UpdateIndexRequest.serialize, + response_deserializer=operations_pb2.Operation.FromString, + ) + return self._stubs["update_index"] + @property def delete_index( self, diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/grpc_asyncio.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/grpc_asyncio.py index c25d0d5e0ebc..9890299b8279 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/grpc_asyncio.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/grpc_asyncio.py @@ -588,6 +588,34 @@ def create_index( ) return self._stubs["create_index"] + @property + def update_index( + self, + ) -> Callable[ + [vectorsearch_service.UpdateIndexRequest], Awaitable[operations_pb2.Operation] + ]: + r"""Return a callable for the update index method over gRPC. + + Updates the parameters of a single Index. + + Returns: + Callable[[~.UpdateIndexRequest], + Awaitable[~.Operation]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "update_index" not in self._stubs: + self._stubs["update_index"] = self._logged_channel.unary_unary( + "/google.cloud.vectorsearch.v1.VectorSearchService/UpdateIndex", + request_serializer=vectorsearch_service.UpdateIndexRequest.serialize, + response_deserializer=operations_pb2.Operation.FromString, + ) + return self._stubs["update_index"] + @property def delete_index( self, @@ -791,6 +819,11 @@ def _prep_wrapped_messages(self, client_info): default_timeout=60.0, client_info=client_info, ), + self.update_index: self._wrap_method( + self.update_index, + default_timeout=None, + client_info=client_info, + ), self.delete_index: self._wrap_method( self.delete_index, default_retry=retries.AsyncRetry( diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/rest.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/rest.py index a41a134036bf..3a709f1f7cae 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/rest.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/rest.py @@ -162,6 +162,14 @@ def post_update_collection(self, response): logging.log(f"Received response: {response}") return response + def pre_update_index(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_index(self, response): + logging.log(f"Received response: {response}") + return response + transport = VectorSearchServiceRestTransport(interceptor=MyCustomVectorSearchServiceInterceptor()) client = VectorSearchServiceClient(transport=transport) @@ -711,6 +719,54 @@ def post_update_collection_with_metadata( """ return response, metadata + def pre_update_index( + self, + request: vectorsearch_service.UpdateIndexRequest, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[ + vectorsearch_service.UpdateIndexRequest, Sequence[Tuple[str, Union[str, bytes]]] + ]: + """Pre-rpc interceptor for update_index + + Override in a subclass to manipulate the request or metadata + before they are sent to the VectorSearchService server. + """ + return request, metadata + + def post_update_index( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for update_index + + DEPRECATED. Please use the `post_update_index_with_metadata` + interceptor instead. + + Override in a subclass to read or manipulate the response + after it is returned by the VectorSearchService server but before + it is returned to user code. This `post_update_index` interceptor runs + before the `post_update_index_with_metadata` interceptor. + """ + return response + + def post_update_index_with_metadata( + self, + response: operations_pb2.Operation, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]: + """Post-rpc interceptor for update_index + + Override in a subclass to read or manipulate the response or metadata after it + is returned by the VectorSearchService server but before it is returned to user code. + + We recommend only using this `post_update_index_with_metadata` + interceptor in new development instead of the `post_update_index` interceptor. + When both interceptors are used, this `post_update_index_with_metadata` interceptor runs after the + `post_update_index` interceptor. The (possibly modified) response returned by + `post_update_index` will be passed to + `post_update_index_with_metadata`. + """ + return response, metadata + def pre_get_location( self, request: locations_pb2.GetLocationRequest, @@ -2649,6 +2705,156 @@ def __call__( ) return resp + class _UpdateIndex( + _BaseVectorSearchServiceRestTransport._BaseUpdateIndex, + VectorSearchServiceRestStub, + ): + def __hash__(self): + return hash("VectorSearchServiceRestTransport.UpdateIndex") + + @staticmethod + def _get_response( + host, + metadata, + query_params, + session, + timeout, + transcoded_request, + body=None, + ): + uri = transcoded_request["uri"] + method = transcoded_request["method"] + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(session, method)( + "{host}{uri}".format(host=host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + return response + + def __call__( + self, + request: vectorsearch_service.UpdateIndexRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> operations_pb2.Operation: + r"""Call the update index method over HTTP. + + Args: + request (~.vectorsearch_service.UpdateIndexRequest): + The request object. Message for updating an Index. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be + sent along with the request as metadata. Normally, each value must be of type `str`, + but for metadata keys ending with the suffix `-bin`, the corresponding values must + be of type `bytes`. + + Returns: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options = _BaseVectorSearchServiceRestTransport._BaseUpdateIndex._get_http_options() + + request, metadata = self._interceptor.pre_update_index(request, metadata) + transcoded_request = _BaseVectorSearchServiceRestTransport._BaseUpdateIndex._get_transcoded_request( + http_options, request + ) + + body = _BaseVectorSearchServiceRestTransport._BaseUpdateIndex._get_request_body_json( + transcoded_request + ) + + # Jsonify the query params + query_params = _BaseVectorSearchServiceRestTransport._BaseUpdateIndex._get_query_params_json( + transcoded_request + ) + + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + request_url = "{host}{uri}".format( + host=self._host, uri=transcoded_request["uri"] + ) + method = transcoded_request["method"] + try: + request_payload = type(request).to_json(request) + except: + request_payload = None + http_request = { + "payload": request_payload, + "requestMethod": method, + "requestUrl": request_url, + "headers": dict(metadata), + } + _LOGGER.debug( + f"Sending request for google.cloud.vectorsearch_v1.VectorSearchServiceClient.UpdateIndex", + extra={ + "serviceName": "google.cloud.vectorsearch.v1.VectorSearchService", + "rpcName": "UpdateIndex", + "httpRequest": http_request, + "metadata": http_request["headers"], + }, + ) + + # Send the request + response = VectorSearchServiceRestTransport._UpdateIndex._get_response( + self._host, + metadata, + query_params, + self._session, + timeout, + transcoded_request, + body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + + resp = self._interceptor.post_update_index(resp) + response_metadata = [(k, str(v)) for k, v in response.headers.items()] + resp, _ = self._interceptor.post_update_index_with_metadata( + resp, response_metadata + ) + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + try: + response_payload = json_format.MessageToJson(resp) + except: + response_payload = None + http_response = { + "payload": response_payload, + "headers": dict(response.headers), + "status": response.status_code, + } + _LOGGER.debug( + "Received response for google.cloud.vectorsearch_v1.VectorSearchServiceClient.update_index", + extra={ + "serviceName": "google.cloud.vectorsearch.v1.VectorSearchService", + "rpcName": "UpdateIndex", + "metadata": http_response["headers"], + "httpResponse": http_response, + }, + ) + return resp + @property def create_collection( self, @@ -2755,6 +2961,14 @@ def update_collection( # In C++ this would require a dynamic_cast return self._UpdateCollection(self._session, self._host, self._interceptor) # type: ignore + @property + def update_index( + self, + ) -> Callable[[vectorsearch_service.UpdateIndexRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateIndex(self._session, self._host, self._interceptor) # type: ignore + @property def get_location(self): return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/rest_base.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/rest_base.py index 64273be5948e..c13c17ad195f 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/rest_base.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/services/vector_search_service/transports/rest_base.py @@ -660,6 +660,63 @@ def _get_query_params_json(transcoded_request): query_params["$alt"] = "json;enum-encoding=int" return query_params + class _BaseUpdateIndex: + def __hash__(self): # pragma: NO COVER + return NotImplementedError("__hash__ must be implemented.") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + @staticmethod + def _get_http_options(): + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v1/{index.name=projects/*/locations/*/collections/*/indexes/*}", + "body": "index", + }, + ] + return http_options + + @staticmethod + def _get_transcoded_request(http_options, request): + pb_request = vectorsearch_service.UpdateIndexRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + return transcoded_request + + @staticmethod + def _get_request_body_json(transcoded_request): + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], use_integers_for_enums=True + ) + return body + + @staticmethod + def _get_query_params_json(transcoded_request): + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + use_integers_for_enums=True, + ) + ) + query_params.update( + _BaseVectorSearchServiceRestTransport._BaseUpdateIndex._get_unset_required_fields( + query_params + ) + ) + + query_params["$alt"] = "json;enum-encoding=int" + return query_params + class _BaseGetLocation: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/types/__init__.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/types/__init__.py index 021fa2299250..fabe8731e3e1 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/types/__init__.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/types/__init__.py @@ -57,6 +57,9 @@ EmbeddingTaskType, VertexEmbeddingConfig, ) +from .encryption_spec import ( + EncryptionSpec, +) from .vectorsearch_service import ( Collection, CreateCollectionRequest, @@ -82,6 +85,7 @@ OperationMetadata, SparseVectorField, UpdateCollectionRequest, + UpdateIndexRequest, VectorField, ) @@ -120,6 +124,7 @@ "UpdateDataObjectRequest", "VertexEmbeddingConfig", "EmbeddingTaskType", + "EncryptionSpec", "Collection", "CreateCollectionRequest", "CreateIndexRequest", @@ -144,5 +149,6 @@ "OperationMetadata", "SparseVectorField", "UpdateCollectionRequest", + "UpdateIndexRequest", "VectorField", ) diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/types/encryption_spec.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/types/encryption_spec.py new file mode 100644 index 000000000000..39d548896ed7 --- /dev/null +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/types/encryption_spec.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright 2025 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. +# +from __future__ import annotations + +from typing import MutableMapping, MutableSequence + +import proto # type: ignore + +__protobuf__ = proto.module( + package="google.cloud.vectorsearch.v1", + manifest={ + "EncryptionSpec", + }, +) + + +class EncryptionSpec(proto.Message): + r"""Represents a customer-managed encryption key specification + that can be applied to a Vector Search collection. + + Attributes: + crypto_key_name (str): + Required. Resource name of the Cloud KMS key used to protect + the resource. + + The Cloud KMS key must be in the same region as the + resource. It must have the format + ``projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}``. + """ + + crypto_key_name: str = proto.Field( + proto.STRING, + number=1, + ) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/types/vectorsearch_service.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/types/vectorsearch_service.py index 79081d38d419..86364d84aa1c 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/types/vectorsearch_service.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1/types/vectorsearch_service.py @@ -24,6 +24,7 @@ import proto # type: ignore from google.cloud.vectorsearch_v1.types import common, embedding_config +from google.cloud.vectorsearch_v1.types import encryption_spec as gcv_encryption_spec __protobuf__ = proto.module( package="google.cloud.vectorsearch.v1", @@ -40,6 +41,7 @@ "DeleteCollectionRequest", "Index", "CreateIndexRequest", + "UpdateIndexRequest", "DeleteIndexRequest", "ListIndexesRequest", "ListIndexesResponse", @@ -81,9 +83,16 @@ class Collection(proto.Message): Field names must contain only alphanumeric characters, underscores, and hyphens. data_schema (google.protobuf.struct_pb2.Struct): - Optional. JSON Schema for data. - Field names must contain only alphanumeric - characters, underscores, and hyphens. + Optional. JSON Schema for data. Field names must contain + only alphanumeric characters, underscores, and hyphens. The + schema must be compliant with `JSON Schema Draft + 7 `__. + encryption_spec (google.cloud.vectorsearch_v1.types.EncryptionSpec): + Optional. Immutable. Specifies the + customer-managed encryption key spec for a + Collection. If set, this Collection and all + sub-resources of this Collection will be secured + by this key. """ name: str = proto.Field( @@ -124,6 +133,11 @@ class Collection(proto.Message): number=10, message=struct_pb2.Struct, ) + encryption_spec: gcv_encryption_spec.EncryptionSpec = proto.Field( + proto.MESSAGE, + number=11, + message=gcv_encryption_spec.EncryptionSpec, + ) class VectorField(proto.Message): @@ -595,6 +609,68 @@ class CreateIndexRequest(proto.Message): ) +class UpdateIndexRequest(proto.Message): + r"""Message for updating an Index. + + Attributes: + index (google.cloud.vectorsearch_v1.types.Index): + Required. The resource being updated. + update_mask (google.protobuf.field_mask_pb2.FieldMask): + Optional. Specifies the fields to be overwritten in the + Index resource by the update. The fields specified in the + update_mask are relative to the resource, not the full + request. A field will be overwritten if it is in the mask. + If the user does not provide a mask then all fields present + in the request with non-empty values will be overwritten. + + The following fields support update: + + - ``display_name`` + - ``description`` + - ``labels`` + - ``dedicated_infrastructure.autoscaling_spec.min_replica_count`` + - ``dedicated_infrastructure.autoscaling_spec.max_replica_count`` + + If ``*`` is provided in the ``update_mask``, full + replacement of mutable fields will be performed. + request_id (str): + Optional. An optional request ID to identify + requests. Specify a unique request ID so that if + you must retry your request, the server will + know to ignore the request if it has already + been completed. The server will guarantee that + for at least 60 minutes since the first request. + + For example, consider a situation where you make + an initial request and the request times out. If + you make the request again with the same request + ID, the server can check if original operation + with the same request ID was received, and if + so, will ignore the second request. This + prevents clients from accidentally creating + duplicate commitments. + + The request ID must be a valid UUID with the + exception that zero UUID is not supported + (00000000-0000-0000-0000-000000000000). + """ + + index: "Index" = proto.Field( + proto.MESSAGE, + number=1, + message="Index", + ) + update_mask: field_mask_pb2.FieldMask = proto.Field( + proto.MESSAGE, + number=2, + message=field_mask_pb2.FieldMask, + ) + request_id: str = proto.Field( + proto.STRING, + number=3, + ) + + class DeleteIndexRequest(proto.Message): r"""Message for deleting an Index. diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/__init__.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/__init__.py index 31f25c39487c..fd7cc4ca36f8 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/__init__.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/__init__.py @@ -76,6 +76,7 @@ UpdateDataObjectRequest, ) from .types.embedding_config import EmbeddingTaskType, VertexEmbeddingConfig +from .types.encryption_spec import EncryptionSpec from .types.vectorsearch_service import ( Collection, CreateCollectionRequest, @@ -101,6 +102,7 @@ OperationMetadata, SparseVectorField, UpdateCollectionRequest, + UpdateIndexRequest, VectorField, ) @@ -228,6 +230,7 @@ def _get_version(dependency_name): "DenseVectorField", "DistanceMetric", "EmbeddingTaskType", + "EncryptionSpec", "ExportDataObjectsMetadata", "ExportDataObjectsRequest", "ExportDataObjectsResponse", @@ -260,6 +263,7 @@ def _get_version(dependency_name): "TextSearch", "UpdateCollectionRequest", "UpdateDataObjectRequest", + "UpdateIndexRequest", "Vector", "VectorField", "VectorSearch", diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/gapic_metadata.json b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/gapic_metadata.json index 2b6221837e22..11148d00adf1 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/gapic_metadata.json +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/gapic_metadata.json @@ -267,6 +267,11 @@ "methods": [ "update_collection" ] + }, + "UpdateIndex": { + "methods": [ + "update_index" + ] } } }, @@ -327,6 +332,11 @@ "methods": [ "update_collection" ] + }, + "UpdateIndex": { + "methods": [ + "update_index" + ] } } }, @@ -387,6 +397,11 @@ "methods": [ "update_collection" ] + }, + "UpdateIndex": { + "methods": [ + "update_index" + ] } } } diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/async_client.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/async_client.py index 4a7759fc8b32..1b20347191a0 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/async_client.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/async_client.py @@ -54,7 +54,11 @@ from google.longrunning import operations_pb2 # type: ignore from google.cloud.vectorsearch_v1beta.services.vector_search_service import pagers -from google.cloud.vectorsearch_v1beta.types import common, vectorsearch_service +from google.cloud.vectorsearch_v1beta.types import ( + common, + encryption_spec, + vectorsearch_service, +) from .client import VectorSearchServiceClient from .transports.base import DEFAULT_CLIENT_INFO, VectorSearchServiceTransport @@ -92,6 +96,10 @@ class VectorSearchServiceAsyncClient: parse_collection_path = staticmethod( VectorSearchServiceClient.parse_collection_path ) + crypto_key_path = staticmethod(VectorSearchServiceClient.crypto_key_path) + parse_crypto_key_path = staticmethod( + VectorSearchServiceClient.parse_crypto_key_path + ) index_path = staticmethod(VectorSearchServiceClient.index_path) parse_index_path = staticmethod(VectorSearchServiceClient.parse_index_path) common_billing_account_path = staticmethod( @@ -1375,6 +1383,160 @@ async def sample_create_index(): # Done; return the response. return response + async def update_index( + self, + request: Optional[Union[vectorsearch_service.UpdateIndexRequest, dict]] = None, + *, + index: Optional[vectorsearch_service.Index] = None, + update_mask: Optional[field_mask_pb2.FieldMask] = None, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> operation_async.AsyncOperation: + r"""Updates the parameters of a single Index. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import vectorsearch_v1beta + + async def sample_update_index(): + # Create a client + client = vectorsearch_v1beta.VectorSearchServiceAsyncClient() + + # Initialize request argument(s) + index = vectorsearch_v1beta.Index() + index.index_field = "index_field_value" + + request = vectorsearch_v1beta.UpdateIndexRequest( + index=index, + ) + + # Make the request + operation = client.update_index(request=request) + + print("Waiting for operation to complete...") + + response = (await operation).result() + + # Handle the response + print(response) + + Args: + request (Optional[Union[google.cloud.vectorsearch_v1beta.types.UpdateIndexRequest, dict]]): + The request object. Message for updating an Index. + index (:class:`google.cloud.vectorsearch_v1beta.types.Index`): + Required. The resource being updated. + This corresponds to the ``index`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + update_mask (:class:`google.protobuf.field_mask_pb2.FieldMask`): + Optional. Specifies the fields to be overwritten in the + Index resource by the update. The fields specified in + the update_mask are relative to the resource, not the + full request. A field will be overwritten if it is in + the mask. If the user does not provide a mask then all + fields present in the request with non-empty values will + be overwritten. + + The following fields support update: + + - ``display_name`` + - ``description`` + - ``labels`` + - ``dedicated_infrastructure.autoscaling_spec.min_replica_count`` + - ``dedicated_infrastructure.autoscaling_spec.max_replica_count`` + + If ``*`` is provided in the ``update_mask``, full + replacement of mutable fields will be performed. + + This corresponds to the ``update_mask`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + retry (google.api_core.retry_async.AsyncRetry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be + sent along with the request as metadata. Normally, each value must be of type `str`, + but for metadata keys ending with the suffix `-bin`, the corresponding values must + be of type `bytes`. + + Returns: + google.api_core.operation_async.AsyncOperation: + An object representing a long-running operation. + + The result type for the operation will be + :class:`google.cloud.vectorsearch_v1beta.types.Index` + Message describing Index object + + """ + # Create or coerce a protobuf request object. + # - Quick check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + flattened_params = [index, update_mask] + has_flattened_params = ( + len([param for param in flattened_params if param is not None]) > 0 + ) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # - Use the request object if provided (there's no risk of modifying the input as + # there are no flattened fields), or create one. + if not isinstance(request, vectorsearch_service.UpdateIndexRequest): + request = vectorsearch_service.UpdateIndexRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + if index is not None: + request.index = index + if update_mask is not None: + request.update_mask = update_mask + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._client._transport._wrapped_methods[ + self._client._transport.update_index + ] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata( + (("index.name", request.index.name),) + ), + ) + + # Validate the universe domain. + self._client._validate_universe_domain() + + # Send the request. + response = await rpc( + request, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Wrap the response in an operation future. + response = operation_async.from_gapic( + response, + self._client._transport.operations_client, + vectorsearch_service.Index, + metadata_type=vectorsearch_service.OperationMetadata, + ) + + # Done; return the response. + return response + async def delete_index( self, request: Optional[Union[vectorsearch_service.DeleteIndexRequest, dict]] = None, @@ -1647,7 +1809,7 @@ async def sample_export_data_objects(): # Initialize request argument(s) gcs_destination = vectorsearch_v1beta.GcsExportDestination() gcs_destination.export_uri = "export_uri_value" - gcs_destination.format_ = "JSON" + gcs_destination.format_ = "JSONL" request = vectorsearch_v1beta.ExportDataObjectsRequest( gcs_destination=gcs_destination, diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/client.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/client.py index 1a27481e0052..369eab3b2b20 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/client.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/client.py @@ -71,7 +71,11 @@ from google.longrunning import operations_pb2 # type: ignore from google.cloud.vectorsearch_v1beta.services.vector_search_service import pagers -from google.cloud.vectorsearch_v1beta.types import common, vectorsearch_service +from google.cloud.vectorsearch_v1beta.types import ( + common, + encryption_spec, + vectorsearch_service, +) from .transports.base import DEFAULT_CLIENT_INFO, VectorSearchServiceTransport from .transports.grpc import VectorSearchServiceGrpcTransport @@ -265,6 +269,30 @@ def parse_collection_path(path: str) -> Dict[str, str]: ) return m.groupdict() if m else {} + @staticmethod + def crypto_key_path( + project: str, + location: str, + key_ring: str, + crypto_key: str, + ) -> str: + """Returns a fully-qualified crypto_key string.""" + return "projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}".format( + project=project, + location=location, + key_ring=key_ring, + crypto_key=crypto_key, + ) + + @staticmethod + def parse_crypto_key_path(path: str) -> Dict[str, str]: + """Parses a crypto_key path into its component segments.""" + m = re.match( + r"^projects/(?P.+?)/locations/(?P.+?)/keyRings/(?P.+?)/cryptoKeys/(?P.+?)$", + path, + ) + return m.groupdict() if m else {} + @staticmethod def index_path( project: str, @@ -1806,6 +1834,157 @@ def sample_create_index(): # Done; return the response. return response + def update_index( + self, + request: Optional[Union[vectorsearch_service.UpdateIndexRequest, dict]] = None, + *, + index: Optional[vectorsearch_service.Index] = None, + update_mask: Optional[field_mask_pb2.FieldMask] = None, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> operation.Operation: + r"""Updates the parameters of a single Index. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import vectorsearch_v1beta + + def sample_update_index(): + # Create a client + client = vectorsearch_v1beta.VectorSearchServiceClient() + + # Initialize request argument(s) + index = vectorsearch_v1beta.Index() + index.index_field = "index_field_value" + + request = vectorsearch_v1beta.UpdateIndexRequest( + index=index, + ) + + # Make the request + operation = client.update_index(request=request) + + print("Waiting for operation to complete...") + + response = operation.result() + + # Handle the response + print(response) + + Args: + request (Union[google.cloud.vectorsearch_v1beta.types.UpdateIndexRequest, dict]): + The request object. Message for updating an Index. + index (google.cloud.vectorsearch_v1beta.types.Index): + Required. The resource being updated. + This corresponds to the ``index`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + update_mask (google.protobuf.field_mask_pb2.FieldMask): + Optional. Specifies the fields to be overwritten in the + Index resource by the update. The fields specified in + the update_mask are relative to the resource, not the + full request. A field will be overwritten if it is in + the mask. If the user does not provide a mask then all + fields present in the request with non-empty values will + be overwritten. + + The following fields support update: + + - ``display_name`` + - ``description`` + - ``labels`` + - ``dedicated_infrastructure.autoscaling_spec.min_replica_count`` + - ``dedicated_infrastructure.autoscaling_spec.max_replica_count`` + + If ``*`` is provided in the ``update_mask``, full + replacement of mutable fields will be performed. + + This corresponds to the ``update_mask`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be + sent along with the request as metadata. Normally, each value must be of type `str`, + but for metadata keys ending with the suffix `-bin`, the corresponding values must + be of type `bytes`. + + Returns: + google.api_core.operation.Operation: + An object representing a long-running operation. + + The result type for the operation will be + :class:`google.cloud.vectorsearch_v1beta.types.Index` + Message describing Index object + + """ + # Create or coerce a protobuf request object. + # - Quick check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + flattened_params = [index, update_mask] + has_flattened_params = ( + len([param for param in flattened_params if param is not None]) > 0 + ) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # - Use the request object if provided (there's no risk of modifying the input as + # there are no flattened fields), or create one. + if not isinstance(request, vectorsearch_service.UpdateIndexRequest): + request = vectorsearch_service.UpdateIndexRequest(request) + # If we have keyword arguments corresponding to fields on the + # request, apply these. + if index is not None: + request.index = index + if update_mask is not None: + request.update_mask = update_mask + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.update_index] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata( + (("index.name", request.index.name),) + ), + ) + + # Validate the universe domain. + self._validate_universe_domain() + + # Send the request. + response = rpc( + request, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Wrap the response in an operation future. + response = operation.from_gapic( + response, + self._transport.operations_client, + vectorsearch_service.Index, + metadata_type=vectorsearch_service.OperationMetadata, + ) + + # Done; return the response. + return response + def delete_index( self, request: Optional[Union[vectorsearch_service.DeleteIndexRequest, dict]] = None, @@ -2073,7 +2252,7 @@ def sample_export_data_objects(): # Initialize request argument(s) gcs_destination = vectorsearch_v1beta.GcsExportDestination() gcs_destination.export_uri = "export_uri_value" - gcs_destination.format_ = "JSON" + gcs_destination.format_ = "JSONL" request = vectorsearch_v1beta.ExportDataObjectsRequest( gcs_destination=gcs_destination, diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/base.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/base.py index a402ed1de827..1d915c099ff5 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/base.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/base.py @@ -256,6 +256,11 @@ def _prep_wrapped_messages(self, client_info): default_timeout=60.0, client_info=client_info, ), + self.update_index: gapic_v1.method.wrap_method( + self.update_index, + default_timeout=None, + client_info=client_info, + ), self.delete_index: gapic_v1.method.wrap_method( self.delete_index, default_retry=retries.Retry( @@ -415,6 +420,15 @@ def create_index( ]: raise NotImplementedError() + @property + def update_index( + self, + ) -> Callable[ + [vectorsearch_service.UpdateIndexRequest], + Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]], + ]: + raise NotImplementedError() + @property def delete_index( self, diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/grpc.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/grpc.py index fea2315dcc5b..896427e9e9d0 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/grpc.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/grpc.py @@ -572,6 +572,32 @@ def create_index( ) return self._stubs["create_index"] + @property + def update_index( + self, + ) -> Callable[[vectorsearch_service.UpdateIndexRequest], operations_pb2.Operation]: + r"""Return a callable for the update index method over gRPC. + + Updates the parameters of a single Index. + + Returns: + Callable[[~.UpdateIndexRequest], + ~.Operation]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "update_index" not in self._stubs: + self._stubs["update_index"] = self._logged_channel.unary_unary( + "/google.cloud.vectorsearch.v1beta.VectorSearchService/UpdateIndex", + request_serializer=vectorsearch_service.UpdateIndexRequest.serialize, + response_deserializer=operations_pb2.Operation.FromString, + ) + return self._stubs["update_index"] + @property def delete_index( self, diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/grpc_asyncio.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/grpc_asyncio.py index 9456fa9adc19..2d91debbc9e8 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/grpc_asyncio.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/grpc_asyncio.py @@ -588,6 +588,34 @@ def create_index( ) return self._stubs["create_index"] + @property + def update_index( + self, + ) -> Callable[ + [vectorsearch_service.UpdateIndexRequest], Awaitable[operations_pb2.Operation] + ]: + r"""Return a callable for the update index method over gRPC. + + Updates the parameters of a single Index. + + Returns: + Callable[[~.UpdateIndexRequest], + Awaitable[~.Operation]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "update_index" not in self._stubs: + self._stubs["update_index"] = self._logged_channel.unary_unary( + "/google.cloud.vectorsearch.v1beta.VectorSearchService/UpdateIndex", + request_serializer=vectorsearch_service.UpdateIndexRequest.serialize, + response_deserializer=operations_pb2.Operation.FromString, + ) + return self._stubs["update_index"] + @property def delete_index( self, @@ -791,6 +819,11 @@ def _prep_wrapped_messages(self, client_info): default_timeout=60.0, client_info=client_info, ), + self.update_index: self._wrap_method( + self.update_index, + default_timeout=None, + client_info=client_info, + ), self.delete_index: self._wrap_method( self.delete_index, default_retry=retries.AsyncRetry( diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/rest.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/rest.py index b3cb75d90b93..64abce0639cf 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/rest.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/rest.py @@ -162,6 +162,14 @@ def post_update_collection(self, response): logging.log(f"Received response: {response}") return response + def pre_update_index(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_index(self, response): + logging.log(f"Received response: {response}") + return response + transport = VectorSearchServiceRestTransport(interceptor=MyCustomVectorSearchServiceInterceptor()) client = VectorSearchServiceClient(transport=transport) @@ -711,6 +719,54 @@ def post_update_collection_with_metadata( """ return response, metadata + def pre_update_index( + self, + request: vectorsearch_service.UpdateIndexRequest, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[ + vectorsearch_service.UpdateIndexRequest, Sequence[Tuple[str, Union[str, bytes]]] + ]: + """Pre-rpc interceptor for update_index + + Override in a subclass to manipulate the request or metadata + before they are sent to the VectorSearchService server. + """ + return request, metadata + + def post_update_index( + self, response: operations_pb2.Operation + ) -> operations_pb2.Operation: + """Post-rpc interceptor for update_index + + DEPRECATED. Please use the `post_update_index_with_metadata` + interceptor instead. + + Override in a subclass to read or manipulate the response + after it is returned by the VectorSearchService server but before + it is returned to user code. This `post_update_index` interceptor runs + before the `post_update_index_with_metadata` interceptor. + """ + return response + + def post_update_index_with_metadata( + self, + response: operations_pb2.Operation, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[operations_pb2.Operation, Sequence[Tuple[str, Union[str, bytes]]]]: + """Post-rpc interceptor for update_index + + Override in a subclass to read or manipulate the response or metadata after it + is returned by the VectorSearchService server but before it is returned to user code. + + We recommend only using this `post_update_index_with_metadata` + interceptor in new development instead of the `post_update_index` interceptor. + When both interceptors are used, this `post_update_index_with_metadata` interceptor runs after the + `post_update_index` interceptor. The (possibly modified) response returned by + `post_update_index` will be passed to + `post_update_index_with_metadata`. + """ + return response, metadata + def pre_get_location( self, request: locations_pb2.GetLocationRequest, @@ -2649,6 +2705,156 @@ def __call__( ) return resp + class _UpdateIndex( + _BaseVectorSearchServiceRestTransport._BaseUpdateIndex, + VectorSearchServiceRestStub, + ): + def __hash__(self): + return hash("VectorSearchServiceRestTransport.UpdateIndex") + + @staticmethod + def _get_response( + host, + metadata, + query_params, + session, + timeout, + transcoded_request, + body=None, + ): + uri = transcoded_request["uri"] + method = transcoded_request["method"] + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(session, method)( + "{host}{uri}".format(host=host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + return response + + def __call__( + self, + request: vectorsearch_service.UpdateIndexRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> operations_pb2.Operation: + r"""Call the update index method over HTTP. + + Args: + request (~.vectorsearch_service.UpdateIndexRequest): + The request object. Message for updating an Index. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be + sent along with the request as metadata. Normally, each value must be of type `str`, + but for metadata keys ending with the suffix `-bin`, the corresponding values must + be of type `bytes`. + + Returns: + ~.operations_pb2.Operation: + This resource represents a + long-running operation that is the + result of a network API call. + + """ + + http_options = _BaseVectorSearchServiceRestTransport._BaseUpdateIndex._get_http_options() + + request, metadata = self._interceptor.pre_update_index(request, metadata) + transcoded_request = _BaseVectorSearchServiceRestTransport._BaseUpdateIndex._get_transcoded_request( + http_options, request + ) + + body = _BaseVectorSearchServiceRestTransport._BaseUpdateIndex._get_request_body_json( + transcoded_request + ) + + # Jsonify the query params + query_params = _BaseVectorSearchServiceRestTransport._BaseUpdateIndex._get_query_params_json( + transcoded_request + ) + + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + request_url = "{host}{uri}".format( + host=self._host, uri=transcoded_request["uri"] + ) + method = transcoded_request["method"] + try: + request_payload = type(request).to_json(request) + except: + request_payload = None + http_request = { + "payload": request_payload, + "requestMethod": method, + "requestUrl": request_url, + "headers": dict(metadata), + } + _LOGGER.debug( + f"Sending request for google.cloud.vectorsearch_v1beta.VectorSearchServiceClient.UpdateIndex", + extra={ + "serviceName": "google.cloud.vectorsearch.v1beta.VectorSearchService", + "rpcName": "UpdateIndex", + "httpRequest": http_request, + "metadata": http_request["headers"], + }, + ) + + # Send the request + response = VectorSearchServiceRestTransport._UpdateIndex._get_response( + self._host, + metadata, + query_params, + self._session, + timeout, + transcoded_request, + body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = operations_pb2.Operation() + json_format.Parse(response.content, resp, ignore_unknown_fields=True) + + resp = self._interceptor.post_update_index(resp) + response_metadata = [(k, str(v)) for k, v in response.headers.items()] + resp, _ = self._interceptor.post_update_index_with_metadata( + resp, response_metadata + ) + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + try: + response_payload = json_format.MessageToJson(resp) + except: + response_payload = None + http_response = { + "payload": response_payload, + "headers": dict(response.headers), + "status": response.status_code, + } + _LOGGER.debug( + "Received response for google.cloud.vectorsearch_v1beta.VectorSearchServiceClient.update_index", + extra={ + "serviceName": "google.cloud.vectorsearch.v1beta.VectorSearchService", + "rpcName": "UpdateIndex", + "metadata": http_response["headers"], + "httpResponse": http_response, + }, + ) + return resp + @property def create_collection( self, @@ -2755,6 +2961,14 @@ def update_collection( # In C++ this would require a dynamic_cast return self._UpdateCollection(self._session, self._host, self._interceptor) # type: ignore + @property + def update_index( + self, + ) -> Callable[[vectorsearch_service.UpdateIndexRequest], operations_pb2.Operation]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._UpdateIndex(self._session, self._host, self._interceptor) # type: ignore + @property def get_location(self): return self._GetLocation(self._session, self._host, self._interceptor) # type: ignore diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/rest_base.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/rest_base.py index c7b17892a6e6..4756c804f558 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/rest_base.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/services/vector_search_service/transports/rest_base.py @@ -660,6 +660,63 @@ def _get_query_params_json(transcoded_request): query_params["$alt"] = "json;enum-encoding=int" return query_params + class _BaseUpdateIndex: + def __hash__(self): # pragma: NO COVER + return NotImplementedError("__hash__ must be implemented.") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + @staticmethod + def _get_http_options(): + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v1beta/{index.name=projects/*/locations/*/collections/*/indexes/*}", + "body": "index", + }, + ] + return http_options + + @staticmethod + def _get_transcoded_request(http_options, request): + pb_request = vectorsearch_service.UpdateIndexRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + return transcoded_request + + @staticmethod + def _get_request_body_json(transcoded_request): + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], use_integers_for_enums=True + ) + return body + + @staticmethod + def _get_query_params_json(transcoded_request): + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + use_integers_for_enums=True, + ) + ) + query_params.update( + _BaseVectorSearchServiceRestTransport._BaseUpdateIndex._get_unset_required_fields( + query_params + ) + ) + + query_params["$alt"] = "json;enum-encoding=int" + return query_params + class _BaseGetLocation: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/types/__init__.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/types/__init__.py index ed53e2dd49a0..9b1081bcdc7a 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/types/__init__.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/types/__init__.py @@ -58,6 +58,9 @@ EmbeddingTaskType, VertexEmbeddingConfig, ) +from .encryption_spec import ( + EncryptionSpec, +) from .vectorsearch_service import ( Collection, CreateCollectionRequest, @@ -83,6 +86,7 @@ OperationMetadata, SparseVectorField, UpdateCollectionRequest, + UpdateIndexRequest, VectorField, ) @@ -122,6 +126,7 @@ "UpdateDataObjectRequest", "VertexEmbeddingConfig", "EmbeddingTaskType", + "EncryptionSpec", "Collection", "CreateCollectionRequest", "CreateIndexRequest", @@ -146,5 +151,6 @@ "OperationMetadata", "SparseVectorField", "UpdateCollectionRequest", + "UpdateIndexRequest", "VectorField", ) diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/types/encryption_spec.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/types/encryption_spec.py new file mode 100644 index 000000000000..6c33dea528e7 --- /dev/null +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/types/encryption_spec.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright 2025 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. +# +from __future__ import annotations + +from typing import MutableMapping, MutableSequence + +import proto # type: ignore + +__protobuf__ = proto.module( + package="google.cloud.vectorsearch.v1beta", + manifest={ + "EncryptionSpec", + }, +) + + +class EncryptionSpec(proto.Message): + r"""Represents a customer-managed encryption key specification + that can be applied to a Vector Search collection. + + Attributes: + crypto_key_name (str): + Required. Resource name of the Cloud KMS key used to protect + the resource. + + The Cloud KMS key must be in the same region as the + resource. It must have the format + ``projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}``. + """ + + crypto_key_name: str = proto.Field( + proto.STRING, + number=1, + ) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/types/vectorsearch_service.py b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/types/vectorsearch_service.py index 5d569511e99c..57a02d09644f 100644 --- a/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/types/vectorsearch_service.py +++ b/packages/google-cloud-vectorsearch/google/cloud/vectorsearch_v1beta/types/vectorsearch_service.py @@ -24,6 +24,9 @@ import proto # type: ignore from google.cloud.vectorsearch_v1beta.types import common, embedding_config +from google.cloud.vectorsearch_v1beta.types import ( + encryption_spec as gcv_encryption_spec, +) __protobuf__ = proto.module( package="google.cloud.vectorsearch.v1beta", @@ -40,6 +43,7 @@ "DeleteCollectionRequest", "Index", "CreateIndexRequest", + "UpdateIndexRequest", "DeleteIndexRequest", "ListIndexesRequest", "ListIndexesResponse", @@ -84,9 +88,16 @@ class Collection(proto.Message): Field names must contain only alphanumeric characters, underscores, and hyphens. data_schema (google.protobuf.struct_pb2.Struct): - Optional. JSON Schema for data. - Field names must contain only alphanumeric - characters, underscores, and hyphens. + Optional. JSON Schema for data. Field names must contain + only alphanumeric characters, underscores, and hyphens. The + schema must be compliant with `JSON Schema Draft + 7 `__. + encryption_spec (google.cloud.vectorsearch_v1beta.types.EncryptionSpec): + Optional. Immutable. Specifies the + customer-managed encryption key spec for a + Collection. If set, this Collection and all + sub-resources of this Collection will be secured + by this key. """ name: str = proto.Field( @@ -132,6 +143,11 @@ class Collection(proto.Message): number=10, message=struct_pb2.Struct, ) + encryption_spec: gcv_encryption_spec.EncryptionSpec = proto.Field( + proto.MESSAGE, + number=11, + message=gcv_encryption_spec.EncryptionSpec, + ) class VectorField(proto.Message): @@ -603,6 +619,68 @@ class CreateIndexRequest(proto.Message): ) +class UpdateIndexRequest(proto.Message): + r"""Message for updating an Index. + + Attributes: + index (google.cloud.vectorsearch_v1beta.types.Index): + Required. The resource being updated. + update_mask (google.protobuf.field_mask_pb2.FieldMask): + Optional. Specifies the fields to be overwritten in the + Index resource by the update. The fields specified in the + update_mask are relative to the resource, not the full + request. A field will be overwritten if it is in the mask. + If the user does not provide a mask then all fields present + in the request with non-empty values will be overwritten. + + The following fields support update: + + - ``display_name`` + - ``description`` + - ``labels`` + - ``dedicated_infrastructure.autoscaling_spec.min_replica_count`` + - ``dedicated_infrastructure.autoscaling_spec.max_replica_count`` + + If ``*`` is provided in the ``update_mask``, full + replacement of mutable fields will be performed. + request_id (str): + Optional. An optional request ID to identify + requests. Specify a unique request ID so that if + you must retry your request, the server will + know to ignore the request if it has already + been completed. The server will guarantee that + for at least 60 minutes since the first request. + + For example, consider a situation where you make + an initial request and the request times out. If + you make the request again with the same request + ID, the server can check if original operation + with the same request ID was received, and if + so, will ignore the second request. This + prevents clients from accidentally creating + duplicate commitments. + + The request ID must be a valid UUID with the + exception that zero UUID is not supported + (00000000-0000-0000-0000-000000000000). + """ + + index: "Index" = proto.Field( + proto.MESSAGE, + number=1, + message="Index", + ) + update_mask: field_mask_pb2.FieldMask = proto.Field( + proto.MESSAGE, + number=2, + message=field_mask_pb2.FieldMask, + ) + request_id: str = proto.Field( + proto.STRING, + number=3, + ) + + class DeleteIndexRequest(proto.Message): r"""Message for deleting an Index. @@ -947,12 +1025,15 @@ class Format(proto.Enum): FORMAT_UNSPECIFIED (0): Unspecified format. JSON (1): - The exported Data Objects will be in JSON - format. + Deprecated: Exports Data Objects in ``JSON`` format. Use + ``JSONL`` instead. + JSONL (2): + Exports Data Objects in ``JSONL`` format. """ FORMAT_UNSPECIFIED = 0 JSON = 1 + JSONL = 2 export_uri: str = proto.Field( proto.STRING, @@ -1041,12 +1122,15 @@ class AutoscalingSpec(proto.Message): Attributes: min_replica_count (int): Optional. The minimum number of replicas. If not set or set - to ``0``, defaults to ``2``. Must be >= ``2`` and <= + to ``0``, defaults to ``2``. Must be >= ``1`` and <= ``1000``. max_replica_count (int): - Optional. The maximum number of replicas. If not set or set - to ``0``, defaults to the greater of ``min_replica_count`` - and ``5``. Must be >= ``min_replica_count`` and <= ``1000``. + Optional. The maximum number of replicas. Must be >= + ``min_replica_count`` and <= ``1000``. For the v1beta + version, if not set or set to ``0``, defaults to the greater + of ``min_replica_count`` and ``5``. For all other versions, + if not set or set to ``0``, defaults to the greater of + ``min_replica_count`` and ``2``. """ min_replica_count: int = proto.Field( diff --git a/packages/google-cloud-vectorsearch/samples/generated_samples/snippet_metadata_google.cloud.vectorsearch.v1.json b/packages/google-cloud-vectorsearch/samples/generated_samples/snippet_metadata_google.cloud.vectorsearch.v1.json index cbbd33474a65..ca947e9036d8 100644 --- a/packages/google-cloud-vectorsearch/samples/generated_samples/snippet_metadata_google.cloud.vectorsearch.v1.json +++ b/packages/google-cloud-vectorsearch/samples/generated_samples/snippet_metadata_google.cloud.vectorsearch.v1.json @@ -3564,6 +3564,175 @@ } ], "title": "vectorsearch_v1_generated_vector_search_service_update_collection_sync.py" + }, + { + "canonical": true, + "clientMethod": { + "async": true, + "client": { + "fullName": "google.cloud.vectorsearch_v1.VectorSearchServiceAsyncClient", + "shortName": "VectorSearchServiceAsyncClient" + }, + "fullName": "google.cloud.vectorsearch_v1.VectorSearchServiceAsyncClient.update_index", + "method": { + "fullName": "google.cloud.vectorsearch.v1.VectorSearchService.UpdateIndex", + "service": { + "fullName": "google.cloud.vectorsearch.v1.VectorSearchService", + "shortName": "VectorSearchService" + }, + "shortName": "UpdateIndex" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.vectorsearch_v1.types.UpdateIndexRequest" + }, + { + "name": "index", + "type": "google.cloud.vectorsearch_v1.types.Index" + }, + { + "name": "update_mask", + "type": "google.protobuf.field_mask_pb2.FieldMask" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "resultType": "google.api_core.operation_async.AsyncOperation", + "shortName": "update_index" + }, + "description": "Sample for UpdateIndex", + "file": "vectorsearch_v1_generated_vector_search_service_update_index_async.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "vectorsearch_v1_generated_VectorSearchService_UpdateIndex_async", + "segments": [ + { + "end": 58, + "start": 27, + "type": "FULL" + }, + { + "end": 58, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 48, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 55, + "start": 49, + "type": "REQUEST_EXECUTION" + }, + { + "end": 59, + "start": 56, + "type": "RESPONSE_HANDLING" + } + ], + "title": "vectorsearch_v1_generated_vector_search_service_update_index_async.py" + }, + { + "canonical": true, + "clientMethod": { + "client": { + "fullName": "google.cloud.vectorsearch_v1.VectorSearchServiceClient", + "shortName": "VectorSearchServiceClient" + }, + "fullName": "google.cloud.vectorsearch_v1.VectorSearchServiceClient.update_index", + "method": { + "fullName": "google.cloud.vectorsearch.v1.VectorSearchService.UpdateIndex", + "service": { + "fullName": "google.cloud.vectorsearch.v1.VectorSearchService", + "shortName": "VectorSearchService" + }, + "shortName": "UpdateIndex" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.vectorsearch_v1.types.UpdateIndexRequest" + }, + { + "name": "index", + "type": "google.cloud.vectorsearch_v1.types.Index" + }, + { + "name": "update_mask", + "type": "google.protobuf.field_mask_pb2.FieldMask" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "resultType": "google.api_core.operation.Operation", + "shortName": "update_index" + }, + "description": "Sample for UpdateIndex", + "file": "vectorsearch_v1_generated_vector_search_service_update_index_sync.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "vectorsearch_v1_generated_VectorSearchService_UpdateIndex_sync", + "segments": [ + { + "end": 58, + "start": 27, + "type": "FULL" + }, + { + "end": 58, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 48, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 55, + "start": 49, + "type": "REQUEST_EXECUTION" + }, + { + "end": 59, + "start": 56, + "type": "RESPONSE_HANDLING" + } + ], + "title": "vectorsearch_v1_generated_vector_search_service_update_index_sync.py" } ] } diff --git a/packages/google-cloud-vectorsearch/samples/generated_samples/snippet_metadata_google.cloud.vectorsearch.v1beta.json b/packages/google-cloud-vectorsearch/samples/generated_samples/snippet_metadata_google.cloud.vectorsearch.v1beta.json index b14ae132bad1..da020b7bca8a 100644 --- a/packages/google-cloud-vectorsearch/samples/generated_samples/snippet_metadata_google.cloud.vectorsearch.v1beta.json +++ b/packages/google-cloud-vectorsearch/samples/generated_samples/snippet_metadata_google.cloud.vectorsearch.v1beta.json @@ -3564,6 +3564,175 @@ } ], "title": "vectorsearch_v1beta_generated_vector_search_service_update_collection_sync.py" + }, + { + "canonical": true, + "clientMethod": { + "async": true, + "client": { + "fullName": "google.cloud.vectorsearch_v1beta.VectorSearchServiceAsyncClient", + "shortName": "VectorSearchServiceAsyncClient" + }, + "fullName": "google.cloud.vectorsearch_v1beta.VectorSearchServiceAsyncClient.update_index", + "method": { + "fullName": "google.cloud.vectorsearch.v1beta.VectorSearchService.UpdateIndex", + "service": { + "fullName": "google.cloud.vectorsearch.v1beta.VectorSearchService", + "shortName": "VectorSearchService" + }, + "shortName": "UpdateIndex" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.vectorsearch_v1beta.types.UpdateIndexRequest" + }, + { + "name": "index", + "type": "google.cloud.vectorsearch_v1beta.types.Index" + }, + { + "name": "update_mask", + "type": "google.protobuf.field_mask_pb2.FieldMask" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "resultType": "google.api_core.operation_async.AsyncOperation", + "shortName": "update_index" + }, + "description": "Sample for UpdateIndex", + "file": "vectorsearch_v1beta_generated_vector_search_service_update_index_async.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "vectorsearch_v1beta_generated_VectorSearchService_UpdateIndex_async", + "segments": [ + { + "end": 58, + "start": 27, + "type": "FULL" + }, + { + "end": 58, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 48, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 55, + "start": 49, + "type": "REQUEST_EXECUTION" + }, + { + "end": 59, + "start": 56, + "type": "RESPONSE_HANDLING" + } + ], + "title": "vectorsearch_v1beta_generated_vector_search_service_update_index_async.py" + }, + { + "canonical": true, + "clientMethod": { + "client": { + "fullName": "google.cloud.vectorsearch_v1beta.VectorSearchServiceClient", + "shortName": "VectorSearchServiceClient" + }, + "fullName": "google.cloud.vectorsearch_v1beta.VectorSearchServiceClient.update_index", + "method": { + "fullName": "google.cloud.vectorsearch.v1beta.VectorSearchService.UpdateIndex", + "service": { + "fullName": "google.cloud.vectorsearch.v1beta.VectorSearchService", + "shortName": "VectorSearchService" + }, + "shortName": "UpdateIndex" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.vectorsearch_v1beta.types.UpdateIndexRequest" + }, + { + "name": "index", + "type": "google.cloud.vectorsearch_v1beta.types.Index" + }, + { + "name": "update_mask", + "type": "google.protobuf.field_mask_pb2.FieldMask" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "resultType": "google.api_core.operation.Operation", + "shortName": "update_index" + }, + "description": "Sample for UpdateIndex", + "file": "vectorsearch_v1beta_generated_vector_search_service_update_index_sync.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "vectorsearch_v1beta_generated_VectorSearchService_UpdateIndex_sync", + "segments": [ + { + "end": 58, + "start": 27, + "type": "FULL" + }, + { + "end": 58, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 48, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 55, + "start": 49, + "type": "REQUEST_EXECUTION" + }, + { + "end": 59, + "start": 56, + "type": "RESPONSE_HANDLING" + } + ], + "title": "vectorsearch_v1beta_generated_vector_search_service_update_index_sync.py" } ] } diff --git a/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1_generated_vector_search_service_update_index_async.py b/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1_generated_vector_search_service_update_index_async.py new file mode 100644 index 000000000000..e3c1890f2029 --- /dev/null +++ b/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1_generated_vector_search_service_update_index_async.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Copyright 2025 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. +# +# Generated code. DO NOT EDIT! +# +# Snippet for UpdateIndex +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-vectorsearch + + +# [START vectorsearch_v1_generated_VectorSearchService_UpdateIndex_async] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import vectorsearch_v1 + + +async def sample_update_index(): + # Create a client + client = vectorsearch_v1.VectorSearchServiceAsyncClient() + + # Initialize request argument(s) + index = vectorsearch_v1.Index() + index.index_field = "index_field_value" + + request = vectorsearch_v1.UpdateIndexRequest( + index=index, + ) + + # Make the request + operation = client.update_index(request=request) + + print("Waiting for operation to complete...") + + response = (await operation).result() + + # Handle the response + print(response) + + +# [END vectorsearch_v1_generated_VectorSearchService_UpdateIndex_async] diff --git a/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1_generated_vector_search_service_update_index_sync.py b/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1_generated_vector_search_service_update_index_sync.py new file mode 100644 index 000000000000..e93c9bd6a7ea --- /dev/null +++ b/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1_generated_vector_search_service_update_index_sync.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Copyright 2025 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. +# +# Generated code. DO NOT EDIT! +# +# Snippet for UpdateIndex +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-vectorsearch + + +# [START vectorsearch_v1_generated_VectorSearchService_UpdateIndex_sync] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import vectorsearch_v1 + + +def sample_update_index(): + # Create a client + client = vectorsearch_v1.VectorSearchServiceClient() + + # Initialize request argument(s) + index = vectorsearch_v1.Index() + index.index_field = "index_field_value" + + request = vectorsearch_v1.UpdateIndexRequest( + index=index, + ) + + # Make the request + operation = client.update_index(request=request) + + print("Waiting for operation to complete...") + + response = operation.result() + + # Handle the response + print(response) + + +# [END vectorsearch_v1_generated_VectorSearchService_UpdateIndex_sync] diff --git a/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1beta_generated_vector_search_service_export_data_objects_async.py b/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1beta_generated_vector_search_service_export_data_objects_async.py index 9e6553c01a1a..2ff5aa3ea030 100644 --- a/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1beta_generated_vector_search_service_export_data_objects_async.py +++ b/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1beta_generated_vector_search_service_export_data_objects_async.py @@ -41,7 +41,7 @@ async def sample_export_data_objects(): # Initialize request argument(s) gcs_destination = vectorsearch_v1beta.GcsExportDestination() gcs_destination.export_uri = "export_uri_value" - gcs_destination.format_ = "JSON" + gcs_destination.format_ = "JSONL" request = vectorsearch_v1beta.ExportDataObjectsRequest( gcs_destination=gcs_destination, diff --git a/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1beta_generated_vector_search_service_export_data_objects_sync.py b/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1beta_generated_vector_search_service_export_data_objects_sync.py index 7c5093841754..f0a7fb24785f 100644 --- a/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1beta_generated_vector_search_service_export_data_objects_sync.py +++ b/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1beta_generated_vector_search_service_export_data_objects_sync.py @@ -41,7 +41,7 @@ def sample_export_data_objects(): # Initialize request argument(s) gcs_destination = vectorsearch_v1beta.GcsExportDestination() gcs_destination.export_uri = "export_uri_value" - gcs_destination.format_ = "JSON" + gcs_destination.format_ = "JSONL" request = vectorsearch_v1beta.ExportDataObjectsRequest( gcs_destination=gcs_destination, diff --git a/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1beta_generated_vector_search_service_update_index_async.py b/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1beta_generated_vector_search_service_update_index_async.py new file mode 100644 index 000000000000..5cc98c6cfcfd --- /dev/null +++ b/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1beta_generated_vector_search_service_update_index_async.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Copyright 2025 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. +# +# Generated code. DO NOT EDIT! +# +# Snippet for UpdateIndex +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-vectorsearch + + +# [START vectorsearch_v1beta_generated_VectorSearchService_UpdateIndex_async] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import vectorsearch_v1beta + + +async def sample_update_index(): + # Create a client + client = vectorsearch_v1beta.VectorSearchServiceAsyncClient() + + # Initialize request argument(s) + index = vectorsearch_v1beta.Index() + index.index_field = "index_field_value" + + request = vectorsearch_v1beta.UpdateIndexRequest( + index=index, + ) + + # Make the request + operation = client.update_index(request=request) + + print("Waiting for operation to complete...") + + response = (await operation).result() + + # Handle the response + print(response) + + +# [END vectorsearch_v1beta_generated_VectorSearchService_UpdateIndex_async] diff --git a/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1beta_generated_vector_search_service_update_index_sync.py b/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1beta_generated_vector_search_service_update_index_sync.py new file mode 100644 index 000000000000..968cbd5297cb --- /dev/null +++ b/packages/google-cloud-vectorsearch/samples/generated_samples/vectorsearch_v1beta_generated_vector_search_service_update_index_sync.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Copyright 2025 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. +# +# Generated code. DO NOT EDIT! +# +# Snippet for UpdateIndex +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-vectorsearch + + +# [START vectorsearch_v1beta_generated_VectorSearchService_UpdateIndex_sync] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import vectorsearch_v1beta + + +def sample_update_index(): + # Create a client + client = vectorsearch_v1beta.VectorSearchServiceClient() + + # Initialize request argument(s) + index = vectorsearch_v1beta.Index() + index.index_field = "index_field_value" + + request = vectorsearch_v1beta.UpdateIndexRequest( + index=index, + ) + + # Make the request + operation = client.update_index(request=request) + + print("Waiting for operation to complete...") + + response = operation.result() + + # Handle the response + print(response) + + +# [END vectorsearch_v1beta_generated_VectorSearchService_UpdateIndex_sync] diff --git a/packages/google-cloud-vectorsearch/tests/unit/gapic/vectorsearch_v1/test_vector_search_service.py b/packages/google-cloud-vectorsearch/tests/unit/gapic/vectorsearch_v1/test_vector_search_service.py index 11c1655d4f8c..ffd5e2b09c86 100644 --- a/packages/google-cloud-vectorsearch/tests/unit/gapic/vectorsearch_v1/test_vector_search_service.py +++ b/packages/google-cloud-vectorsearch/tests/unit/gapic/vectorsearch_v1/test_vector_search_service.py @@ -76,6 +76,7 @@ from google.cloud.vectorsearch_v1.types import ( common, embedding_config, + encryption_spec, vectorsearch_service, ) @@ -4552,6 +4553,364 @@ async def test_create_index_flattened_error_async(): ) +@pytest.mark.parametrize( + "request_type", + [ + vectorsearch_service.UpdateIndexRequest, + dict, + ], +) +def test_update_index(request_type, transport: str = "grpc"): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_index), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = operations_pb2.Operation(name="operations/spam") + response = client.update_index(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + request = vectorsearch_service.UpdateIndexRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, future.Future) + + +def test_update_index_non_empty_request_with_auto_populated_field(): + # This test is a coverage failsafe to make sure that UUID4 fields are + # automatically populated, according to AIP-4235, with non-empty requests. + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Populate all string fields in the request which are not UUID4 + # since we want to check that UUID4 are populated automatically + # if they meet the requirements of AIP 4235. + request = vectorsearch_service.UpdateIndexRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_index), "__call__") as call: + call.return_value.name = ( + "foo" # operation_request.operation in compute client(s) expect a string. + ) + client.update_index(request=request) + call.assert_called() + _, args, _ = call.mock_calls[0] + assert args[0] == vectorsearch_service.UpdateIndexRequest() + + +def test_update_index_use_cached_wrapped_rpc(): + # Clients should use _prep_wrapped_messages to create cached wrapped rpcs, + # instead of constructing them on each call + with mock.patch("google.api_core.gapic_v1.method.wrap_method") as wrapper_fn: + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Should wrap all calls on client creation + assert wrapper_fn.call_count > 0 + wrapper_fn.reset_mock() + + # Ensure method has been cached + assert client._transport.update_index in client._transport._wrapped_methods + + # Replace cached wrapped function with mock + mock_rpc = mock.Mock() + mock_rpc.return_value.name = ( + "foo" # operation_request.operation in compute client(s) expect a string. + ) + client._transport._wrapped_methods[client._transport.update_index] = mock_rpc + request = {} + client.update_index(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + # Operation methods call wrapper_fn to build a cached + # client._transport.operations_client instance on first rpc call. + # Subsequent calls should use the cached wrapper + wrapper_fn.reset_mock() + + client.update_index(request) + + # Establish that a new wrapper was not created for this call + assert wrapper_fn.call_count == 0 + assert mock_rpc.call_count == 2 + + +@pytest.mark.asyncio +async def test_update_index_async_use_cached_wrapped_rpc( + transport: str = "grpc_asyncio", +): + # Clients should use _prep_wrapped_messages to create cached wrapped rpcs, + # instead of constructing them on each call + with mock.patch("google.api_core.gapic_v1.method_async.wrap_method") as wrapper_fn: + client = VectorSearchServiceAsyncClient( + credentials=async_anonymous_credentials(), + transport=transport, + ) + + # Should wrap all calls on client creation + assert wrapper_fn.call_count > 0 + wrapper_fn.reset_mock() + + # Ensure method has been cached + assert ( + client._client._transport.update_index + in client._client._transport._wrapped_methods + ) + + # Replace cached wrapped function with mock + mock_rpc = mock.AsyncMock() + mock_rpc.return_value = mock.Mock() + client._client._transport._wrapped_methods[ + client._client._transport.update_index + ] = mock_rpc + + request = {} + await client.update_index(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + # Operation methods call wrapper_fn to build a cached + # client._transport.operations_client instance on first rpc call. + # Subsequent calls should use the cached wrapper + wrapper_fn.reset_mock() + + await client.update_index(request) + + # Establish that a new wrapper was not created for this call + assert wrapper_fn.call_count == 0 + assert mock_rpc.call_count == 2 + + +@pytest.mark.asyncio +async def test_update_index_async( + transport: str = "grpc_asyncio", + request_type=vectorsearch_service.UpdateIndexRequest, +): + client = VectorSearchServiceAsyncClient( + credentials=async_anonymous_credentials(), + transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_index), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + operations_pb2.Operation(name="operations/spam") + ) + response = await client.update_index(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + request = vectorsearch_service.UpdateIndexRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, future.Future) + + +@pytest.mark.asyncio +async def test_update_index_async_from_dict(): + await test_update_index_async(request_type=dict) + + +def test_update_index_field_headers(): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = vectorsearch_service.UpdateIndexRequest() + + request.index.name = "name_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_index), "__call__") as call: + call.return_value = operations_pb2.Operation(name="operations/op") + client.update_index(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ( + "x-goog-request-params", + "index.name=name_value", + ) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_update_index_field_headers_async(): + client = VectorSearchServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = vectorsearch_service.UpdateIndexRequest() + + request.index.name = "name_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_index), "__call__") as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + operations_pb2.Operation(name="operations/op") + ) + await client.update_index(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ( + "x-goog-request-params", + "index.name=name_value", + ) in kw["metadata"] + + +def test_update_index_flattened(): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_index), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = operations_pb2.Operation(name="operations/op") + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.update_index( + index=vectorsearch_service.Index( + dedicated_infrastructure=vectorsearch_service.DedicatedInfrastructure( + mode=vectorsearch_service.DedicatedInfrastructure.Mode.STORAGE_OPTIMIZED + ) + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + arg = args[0].index + mock_val = vectorsearch_service.Index( + dedicated_infrastructure=vectorsearch_service.DedicatedInfrastructure( + mode=vectorsearch_service.DedicatedInfrastructure.Mode.STORAGE_OPTIMIZED + ) + ) + assert arg == mock_val + arg = args[0].update_mask + mock_val = field_mask_pb2.FieldMask(paths=["paths_value"]) + assert arg == mock_val + + +def test_update_index_flattened_error(): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_index( + vectorsearch_service.UpdateIndexRequest(), + index=vectorsearch_service.Index( + dedicated_infrastructure=vectorsearch_service.DedicatedInfrastructure( + mode=vectorsearch_service.DedicatedInfrastructure.Mode.STORAGE_OPTIMIZED + ) + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +@pytest.mark.asyncio +async def test_update_index_flattened_async(): + client = VectorSearchServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_index), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = operations_pb2.Operation(name="operations/op") + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + operations_pb2.Operation(name="operations/spam") + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.update_index( + index=vectorsearch_service.Index( + dedicated_infrastructure=vectorsearch_service.DedicatedInfrastructure( + mode=vectorsearch_service.DedicatedInfrastructure.Mode.STORAGE_OPTIMIZED + ) + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + arg = args[0].index + mock_val = vectorsearch_service.Index( + dedicated_infrastructure=vectorsearch_service.DedicatedInfrastructure( + mode=vectorsearch_service.DedicatedInfrastructure.Mode.STORAGE_OPTIMIZED + ) + ) + assert arg == mock_val + arg = args[0].update_mask + mock_val = field_mask_pb2.FieldMask(paths=["paths_value"]) + assert arg == mock_val + + +@pytest.mark.asyncio +async def test_update_index_flattened_error_async(): + client = VectorSearchServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.update_index( + vectorsearch_service.UpdateIndexRequest(), + index=vectorsearch_service.Index( + dedicated_infrastructure=vectorsearch_service.DedicatedInfrastructure( + mode=vectorsearch_service.DedicatedInfrastructure.Mode.STORAGE_OPTIMIZED + ) + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + @pytest.mark.parametrize( "request_type", [ @@ -7109,6 +7468,208 @@ def test_create_index_rest_flattened_error(transport: str = "rest"): ) +def test_update_index_rest_use_cached_wrapped_rpc(): + # Clients should use _prep_wrapped_messages to create cached wrapped rpcs, + # instead of constructing them on each call + with mock.patch("google.api_core.gapic_v1.method.wrap_method") as wrapper_fn: + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Should wrap all calls on client creation + assert wrapper_fn.call_count > 0 + wrapper_fn.reset_mock() + + # Ensure method has been cached + assert client._transport.update_index in client._transport._wrapped_methods + + # Replace cached wrapped function with mock + mock_rpc = mock.Mock() + mock_rpc.return_value.name = ( + "foo" # operation_request.operation in compute client(s) expect a string. + ) + client._transport._wrapped_methods[client._transport.update_index] = mock_rpc + + request = {} + client.update_index(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + # Operation methods build a cached wrapper on first rpc call + # subsequent calls should use the cached wrapper + wrapper_fn.reset_mock() + + client.update_index(request) + + # Establish that a new wrapper was not created for this call + assert wrapper_fn.call_count == 0 + assert mock_rpc.call_count == 2 + + +def test_update_index_rest_required_fields( + request_type=vectorsearch_service.UpdateIndexRequest, +): + transport_class = transports.VectorSearchServiceRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson(pb_request, use_integers_for_enums=False) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_index._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_index._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "request_id", + "update_mask", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + req.return_value.headers = {"header-1": "value-1", "header-2": "value-2"} + + response = client.update_index(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_index_rest_unset_required_fields(): + transport = transports.VectorSearchServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_index._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "requestId", + "updateMask", + ) + ) + & set(("index",)) + ) + + +def test_update_index_rest_flattened(): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # get arguments that satisfy an http rule for this method + sample_request = { + "index": { + "name": "projects/sample1/locations/sample2/collections/sample3/indexes/sample4" + } + } + + # get truthy value for each flattened field + mock_args = dict( + index=vectorsearch_service.Index( + dedicated_infrastructure=vectorsearch_service.DedicatedInfrastructure( + mode=vectorsearch_service.DedicatedInfrastructure.Mode.STORAGE_OPTIMIZED + ) + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + req.return_value.headers = {"header-1": "value-1", "header-2": "value-2"} + + client.update_index(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v1/{index.name=projects/*/locations/*/collections/*/indexes/*}" + % client.transport._host, + args[1], + ) + + +def test_update_index_rest_flattened_error(transport: str = "rest"): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_index( + vectorsearch_service.UpdateIndexRequest(), + index=vectorsearch_service.Index( + dedicated_infrastructure=vectorsearch_service.DedicatedInfrastructure( + mode=vectorsearch_service.DedicatedInfrastructure.Mode.STORAGE_OPTIMIZED + ) + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + def test_delete_index_rest_use_cached_wrapped_rpc(): # Clients should use _prep_wrapped_messages to create cached wrapped rpcs, # instead of constructing them on each call @@ -7822,6 +8383,27 @@ def test_create_index_empty_call_grpc(): assert args[0] == request_msg +# This test is a coverage failsafe to make sure that totally empty calls, +# i.e. request == None and no flattened fields passed, work. +def test_update_index_empty_call_grpc(): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object(type(client.transport.update_index), "__call__") as call: + call.return_value = operations_pb2.Operation(name="operations/op") + client.update_index(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = vectorsearch_service.UpdateIndexRequest() + + assert args[0] == request_msg + + # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. def test_delete_index_empty_call_grpc(): @@ -8091,12 +8673,37 @@ async def test_get_index_empty_call_grpc_asyncio(): store_fields=["store_fields_value"], ) ) - await client.get_index(request=None) + await client.get_index(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = vectorsearch_service.GetIndexRequest() + + assert args[0] == request_msg + + +# This test is a coverage failsafe to make sure that totally empty calls, +# i.e. request == None and no flattened fields passed, work. +@pytest.mark.asyncio +async def test_create_index_empty_call_grpc_asyncio(): + client = VectorSearchServiceAsyncClient( + credentials=async_anonymous_credentials(), + transport="grpc_asyncio", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object(type(client.transport.create_index), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + operations_pb2.Operation(name="operations/spam") + ) + await client.create_index(request=None) # Establish that the underlying stub method was called. call.assert_called() _, args, _ = call.mock_calls[0] - request_msg = vectorsearch_service.GetIndexRequest() + request_msg = vectorsearch_service.CreateIndexRequest() assert args[0] == request_msg @@ -8104,24 +8711,24 @@ async def test_get_index_empty_call_grpc_asyncio(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @pytest.mark.asyncio -async def test_create_index_empty_call_grpc_asyncio(): +async def test_update_index_empty_call_grpc_asyncio(): client = VectorSearchServiceAsyncClient( credentials=async_anonymous_credentials(), transport="grpc_asyncio", ) # Mock the actual call, and fake the request. - with mock.patch.object(type(client.transport.create_index), "__call__") as call: + with mock.patch.object(type(client.transport.update_index), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( operations_pb2.Operation(name="operations/spam") ) - await client.create_index(request=None) + await client.update_index(request=None) # Establish that the underlying stub method was called. call.assert_called() _, args, _ = call.mock_calls[0] - request_msg = vectorsearch_service.CreateIndexRequest() + request_msg = vectorsearch_service.UpdateIndexRequest() assert args[0] == request_msg @@ -8536,6 +9143,7 @@ def test_create_collection_rest_call_success(request_type): "labels": {}, "vector_schema": {}, "data_schema": {"fields": {}}, + "encryption_spec": {"crypto_key_name": "crypto_key_name_value"}, } # The version of a generated dependency at test runtime may differ from the version used during generation. # Delete any fields which are not present in the current runtime dependency @@ -8743,6 +9351,7 @@ def test_update_collection_rest_call_success(request_type): "labels": {}, "vector_schema": {}, "data_schema": {"fields": {}}, + "encryption_spec": {"crypto_key_name": "crypto_key_name_value"}, } # The version of a generated dependency at test runtime may differ from the version used during generation. # Delete any fields which are not present in the current runtime dependency @@ -9514,6 +10123,224 @@ def test_create_index_rest_interceptors(null_interceptor): post_with_metadata.assert_called_once() +def test_update_index_rest_bad_request( + request_type=vectorsearch_service.UpdateIndexRequest, +): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + # send a request that will satisfy transcoding + request_init = { + "index": { + "name": "projects/sample1/locations/sample2/collections/sample3/indexes/sample4" + } + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with ( + mock.patch.object(Session, "request") as req, + pytest.raises(core_exceptions.BadRequest), + ): + # Wrap the value into a proper Response obj + response_value = mock.Mock() + json_return_value = "" + response_value.json = mock.Mock(return_value={}) + response_value.status_code = 400 + response_value.request = mock.Mock() + req.return_value = response_value + req.return_value.headers = {"header-1": "value-1", "header-2": "value-2"} + client.update_index(request) + + +@pytest.mark.parametrize( + "request_type", + [ + vectorsearch_service.UpdateIndexRequest, + dict, + ], +) +def test_update_index_rest_call_success(request_type): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + # send a request that will satisfy transcoding + request_init = { + "index": { + "name": "projects/sample1/locations/sample2/collections/sample3/indexes/sample4" + } + } + request_init["index"] = { + "dedicated_infrastructure": { + "mode": 1, + "autoscaling_spec": {"min_replica_count": 1803, "max_replica_count": 1805}, + }, + "dense_scann": {"feature_norm_type": 1}, + "name": "projects/sample1/locations/sample2/collections/sample3/indexes/sample4", + "display_name": "display_name_value", + "description": "description_value", + "labels": {}, + "create_time": {"seconds": 751, "nanos": 543}, + "update_time": {}, + "distance_metric": 1, + "index_field": "index_field_value", + "filter_fields": ["filter_fields_value1", "filter_fields_value2"], + "store_fields": ["store_fields_value1", "store_fields_value2"], + } + # The version of a generated dependency at test runtime may differ from the version used during generation. + # Delete any fields which are not present in the current runtime dependency + # See https://github.com/googleapis/gapic-generator-python/issues/1748 + + # Determine if the message type is proto-plus or protobuf + test_field = vectorsearch_service.UpdateIndexRequest.meta.fields["index"] + + def get_message_fields(field): + # Given a field which is a message (composite type), return a list with + # all the fields of the message. + # If the field is not a composite type, return an empty list. + message_fields = [] + + if hasattr(field, "message") and field.message: + is_field_type_proto_plus_type = not hasattr(field.message, "DESCRIPTOR") + + if is_field_type_proto_plus_type: + message_fields = field.message.meta.fields.values() + # Add `# pragma: NO COVER` because there may not be any `*_pb2` field types + else: # pragma: NO COVER + message_fields = field.message.DESCRIPTOR.fields + return message_fields + + runtime_nested_fields = [ + (field.name, nested_field.name) + for field in get_message_fields(test_field) + for nested_field in get_message_fields(field) + ] + + subfields_not_in_runtime = [] + + # For each item in the sample request, create a list of sub fields which are not present at runtime + # Add `# pragma: NO COVER` because this test code will not run if all subfields are present at runtime + for field, value in request_init["index"].items(): # pragma: NO COVER + result = None + is_repeated = False + # For repeated fields + if isinstance(value, list) and len(value): + is_repeated = True + result = value[0] + # For fields where the type is another message + if isinstance(value, dict): + result = value + + if result and hasattr(result, "keys"): + for subfield in result.keys(): + if (field, subfield) not in runtime_nested_fields: + subfields_not_in_runtime.append( + { + "field": field, + "subfield": subfield, + "is_repeated": is_repeated, + } + ) + + # Remove fields from the sample request which are not present in the runtime version of the dependency + # Add `# pragma: NO COVER` because this test code will not run if all subfields are present at runtime + for subfield_to_delete in subfields_not_in_runtime: # pragma: NO COVER + field = subfield_to_delete.get("field") + field_repeated = subfield_to_delete.get("is_repeated") + subfield = subfield_to_delete.get("subfield") + if subfield: + if field_repeated: + for i in range(0, len(request_init["index"][field])): + del request_init["index"][field][i][subfield] + else: + del request_init["index"][field][subfield] + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = mock.Mock() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + response_value.content = json_return_value.encode("UTF-8") + req.return_value = response_value + req.return_value.headers = {"header-1": "value-1", "header-2": "value-2"} + response = client.update_index(request) + + # Establish that the response is the type that we expect. + json_return_value = json_format.MessageToJson(return_value) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_index_rest_interceptors(null_interceptor): + transport = transports.VectorSearchServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.VectorSearchServiceRestInterceptor(), + ) + client = VectorSearchServiceClient(transport=transport) + + with ( + mock.patch.object(type(client.transport._session), "request") as req, + mock.patch.object(path_template, "transcode") as transcode, + mock.patch.object(operation.Operation, "_set_result_from_operation"), + mock.patch.object( + transports.VectorSearchServiceRestInterceptor, "post_update_index" + ) as post, + mock.patch.object( + transports.VectorSearchServiceRestInterceptor, + "post_update_index_with_metadata", + ) as post_with_metadata, + mock.patch.object( + transports.VectorSearchServiceRestInterceptor, "pre_update_index" + ) as pre, + ): + pre.assert_not_called() + post.assert_not_called() + post_with_metadata.assert_not_called() + pb_message = vectorsearch_service.UpdateIndexRequest.pb( + vectorsearch_service.UpdateIndexRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = mock.Mock() + req.return_value.status_code = 200 + req.return_value.headers = {"header-1": "value-1", "header-2": "value-2"} + return_value = json_format.MessageToJson(operations_pb2.Operation()) + req.return_value.content = return_value + + request = vectorsearch_service.UpdateIndexRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + post_with_metadata.return_value = operations_pb2.Operation(), metadata + + client.update_index( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + post_with_metadata.assert_called_once() + + def test_delete_index_rest_bad_request( request_type=vectorsearch_service.DeleteIndexRequest, ): @@ -10443,6 +11270,26 @@ def test_create_index_empty_call_rest(): assert args[0] == request_msg +# This test is a coverage failsafe to make sure that totally empty calls, +# i.e. request == None and no flattened fields passed, work. +def test_update_index_empty_call_rest(): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object(type(client.transport.update_index), "__call__") as call: + client.update_index(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = vectorsearch_service.UpdateIndexRequest() + + assert args[0] == request_msg + + # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. def test_delete_index_empty_call_rest(): @@ -10565,6 +11412,7 @@ def test_vector_search_service_base_transport(): "list_indexes", "get_index", "create_index", + "update_index", "delete_index", "import_data_objects", "export_data_objects", @@ -10870,6 +11718,9 @@ def test_vector_search_service_client_transport_session_collision(transport_name session1 = client1.transport.create_index._session session2 = client2.transport.create_index._session assert session1 != session2 + session1 = client1.transport.update_index._session + session2 = client2.transport.update_index._session + assert session1 != session2 session1 = client1.transport.delete_index._session session2 = client2.transport.delete_index._session assert session1 != session2 @@ -11070,11 +11921,42 @@ def test_parse_collection_path(): assert expected == actual -def test_index_path(): +def test_crypto_key_path(): project = "cuttlefish" location = "mussel" - collection = "winkle" - index = "nautilus" + key_ring = "winkle" + crypto_key = "nautilus" + expected = "projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}".format( + project=project, + location=location, + key_ring=key_ring, + crypto_key=crypto_key, + ) + actual = VectorSearchServiceClient.crypto_key_path( + project, location, key_ring, crypto_key + ) + assert expected == actual + + +def test_parse_crypto_key_path(): + expected = { + "project": "scallop", + "location": "abalone", + "key_ring": "squid", + "crypto_key": "clam", + } + path = VectorSearchServiceClient.crypto_key_path(**expected) + + # Check that the path construction is reversible. + actual = VectorSearchServiceClient.parse_crypto_key_path(path) + assert expected == actual + + +def test_index_path(): + project = "whelk" + location = "octopus" + collection = "oyster" + index = "nudibranch" expected = "projects/{project}/locations/{location}/collections/{collection}/indexes/{index}".format( project=project, location=location, @@ -11087,10 +11969,10 @@ def test_index_path(): def test_parse_index_path(): expected = { - "project": "scallop", - "location": "abalone", - "collection": "squid", - "index": "clam", + "project": "cuttlefish", + "location": "mussel", + "collection": "winkle", + "index": "nautilus", } path = VectorSearchServiceClient.index_path(**expected) @@ -11100,7 +11982,7 @@ def test_parse_index_path(): def test_common_billing_account_path(): - billing_account = "whelk" + billing_account = "scallop" expected = "billingAccounts/{billing_account}".format( billing_account=billing_account, ) @@ -11110,7 +11992,7 @@ def test_common_billing_account_path(): def test_parse_common_billing_account_path(): expected = { - "billing_account": "octopus", + "billing_account": "abalone", } path = VectorSearchServiceClient.common_billing_account_path(**expected) @@ -11120,7 +12002,7 @@ def test_parse_common_billing_account_path(): def test_common_folder_path(): - folder = "oyster" + folder = "squid" expected = "folders/{folder}".format( folder=folder, ) @@ -11130,7 +12012,7 @@ def test_common_folder_path(): def test_parse_common_folder_path(): expected = { - "folder": "nudibranch", + "folder": "clam", } path = VectorSearchServiceClient.common_folder_path(**expected) @@ -11140,7 +12022,7 @@ def test_parse_common_folder_path(): def test_common_organization_path(): - organization = "cuttlefish" + organization = "whelk" expected = "organizations/{organization}".format( organization=organization, ) @@ -11150,7 +12032,7 @@ def test_common_organization_path(): def test_parse_common_organization_path(): expected = { - "organization": "mussel", + "organization": "octopus", } path = VectorSearchServiceClient.common_organization_path(**expected) @@ -11160,7 +12042,7 @@ def test_parse_common_organization_path(): def test_common_project_path(): - project = "winkle" + project = "oyster" expected = "projects/{project}".format( project=project, ) @@ -11170,7 +12052,7 @@ def test_common_project_path(): def test_parse_common_project_path(): expected = { - "project": "nautilus", + "project": "nudibranch", } path = VectorSearchServiceClient.common_project_path(**expected) @@ -11180,8 +12062,8 @@ def test_parse_common_project_path(): def test_common_location_path(): - project = "scallop" - location = "abalone" + project = "cuttlefish" + location = "mussel" expected = "projects/{project}/locations/{location}".format( project=project, location=location, @@ -11192,8 +12074,8 @@ def test_common_location_path(): def test_parse_common_location_path(): expected = { - "project": "squid", - "location": "clam", + "project": "winkle", + "location": "nautilus", } path = VectorSearchServiceClient.common_location_path(**expected) diff --git a/packages/google-cloud-vectorsearch/tests/unit/gapic/vectorsearch_v1beta/test_vector_search_service.py b/packages/google-cloud-vectorsearch/tests/unit/gapic/vectorsearch_v1beta/test_vector_search_service.py index f54c826b88cc..49abfa3d0cf4 100644 --- a/packages/google-cloud-vectorsearch/tests/unit/gapic/vectorsearch_v1beta/test_vector_search_service.py +++ b/packages/google-cloud-vectorsearch/tests/unit/gapic/vectorsearch_v1beta/test_vector_search_service.py @@ -76,6 +76,7 @@ from google.cloud.vectorsearch_v1beta.types import ( common, embedding_config, + encryption_spec, vectorsearch_service, ) @@ -4552,6 +4553,364 @@ async def test_create_index_flattened_error_async(): ) +@pytest.mark.parametrize( + "request_type", + [ + vectorsearch_service.UpdateIndexRequest, + dict, + ], +) +def test_update_index(request_type, transport: str = "grpc"): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_index), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = operations_pb2.Operation(name="operations/spam") + response = client.update_index(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + request = vectorsearch_service.UpdateIndexRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, future.Future) + + +def test_update_index_non_empty_request_with_auto_populated_field(): + # This test is a coverage failsafe to make sure that UUID4 fields are + # automatically populated, according to AIP-4235, with non-empty requests. + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Populate all string fields in the request which are not UUID4 + # since we want to check that UUID4 are populated automatically + # if they meet the requirements of AIP 4235. + request = vectorsearch_service.UpdateIndexRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_index), "__call__") as call: + call.return_value.name = ( + "foo" # operation_request.operation in compute client(s) expect a string. + ) + client.update_index(request=request) + call.assert_called() + _, args, _ = call.mock_calls[0] + assert args[0] == vectorsearch_service.UpdateIndexRequest() + + +def test_update_index_use_cached_wrapped_rpc(): + # Clients should use _prep_wrapped_messages to create cached wrapped rpcs, + # instead of constructing them on each call + with mock.patch("google.api_core.gapic_v1.method.wrap_method") as wrapper_fn: + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Should wrap all calls on client creation + assert wrapper_fn.call_count > 0 + wrapper_fn.reset_mock() + + # Ensure method has been cached + assert client._transport.update_index in client._transport._wrapped_methods + + # Replace cached wrapped function with mock + mock_rpc = mock.Mock() + mock_rpc.return_value.name = ( + "foo" # operation_request.operation in compute client(s) expect a string. + ) + client._transport._wrapped_methods[client._transport.update_index] = mock_rpc + request = {} + client.update_index(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + # Operation methods call wrapper_fn to build a cached + # client._transport.operations_client instance on first rpc call. + # Subsequent calls should use the cached wrapper + wrapper_fn.reset_mock() + + client.update_index(request) + + # Establish that a new wrapper was not created for this call + assert wrapper_fn.call_count == 0 + assert mock_rpc.call_count == 2 + + +@pytest.mark.asyncio +async def test_update_index_async_use_cached_wrapped_rpc( + transport: str = "grpc_asyncio", +): + # Clients should use _prep_wrapped_messages to create cached wrapped rpcs, + # instead of constructing them on each call + with mock.patch("google.api_core.gapic_v1.method_async.wrap_method") as wrapper_fn: + client = VectorSearchServiceAsyncClient( + credentials=async_anonymous_credentials(), + transport=transport, + ) + + # Should wrap all calls on client creation + assert wrapper_fn.call_count > 0 + wrapper_fn.reset_mock() + + # Ensure method has been cached + assert ( + client._client._transport.update_index + in client._client._transport._wrapped_methods + ) + + # Replace cached wrapped function with mock + mock_rpc = mock.AsyncMock() + mock_rpc.return_value = mock.Mock() + client._client._transport._wrapped_methods[ + client._client._transport.update_index + ] = mock_rpc + + request = {} + await client.update_index(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + # Operation methods call wrapper_fn to build a cached + # client._transport.operations_client instance on first rpc call. + # Subsequent calls should use the cached wrapper + wrapper_fn.reset_mock() + + await client.update_index(request) + + # Establish that a new wrapper was not created for this call + assert wrapper_fn.call_count == 0 + assert mock_rpc.call_count == 2 + + +@pytest.mark.asyncio +async def test_update_index_async( + transport: str = "grpc_asyncio", + request_type=vectorsearch_service.UpdateIndexRequest, +): + client = VectorSearchServiceAsyncClient( + credentials=async_anonymous_credentials(), + transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_index), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + operations_pb2.Operation(name="operations/spam") + ) + response = await client.update_index(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + request = vectorsearch_service.UpdateIndexRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, future.Future) + + +@pytest.mark.asyncio +async def test_update_index_async_from_dict(): + await test_update_index_async(request_type=dict) + + +def test_update_index_field_headers(): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = vectorsearch_service.UpdateIndexRequest() + + request.index.name = "name_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_index), "__call__") as call: + call.return_value = operations_pb2.Operation(name="operations/op") + client.update_index(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ( + "x-goog-request-params", + "index.name=name_value", + ) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_update_index_field_headers_async(): + client = VectorSearchServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = vectorsearch_service.UpdateIndexRequest() + + request.index.name = "name_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_index), "__call__") as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + operations_pb2.Operation(name="operations/op") + ) + await client.update_index(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ( + "x-goog-request-params", + "index.name=name_value", + ) in kw["metadata"] + + +def test_update_index_flattened(): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_index), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = operations_pb2.Operation(name="operations/op") + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.update_index( + index=vectorsearch_service.Index( + dedicated_infrastructure=vectorsearch_service.DedicatedInfrastructure( + mode=vectorsearch_service.DedicatedInfrastructure.Mode.STORAGE_OPTIMIZED + ) + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + arg = args[0].index + mock_val = vectorsearch_service.Index( + dedicated_infrastructure=vectorsearch_service.DedicatedInfrastructure( + mode=vectorsearch_service.DedicatedInfrastructure.Mode.STORAGE_OPTIMIZED + ) + ) + assert arg == mock_val + arg = args[0].update_mask + mock_val = field_mask_pb2.FieldMask(paths=["paths_value"]) + assert arg == mock_val + + +def test_update_index_flattened_error(): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_index( + vectorsearch_service.UpdateIndexRequest(), + index=vectorsearch_service.Index( + dedicated_infrastructure=vectorsearch_service.DedicatedInfrastructure( + mode=vectorsearch_service.DedicatedInfrastructure.Mode.STORAGE_OPTIMIZED + ) + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +@pytest.mark.asyncio +async def test_update_index_flattened_async(): + client = VectorSearchServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_index), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = operations_pb2.Operation(name="operations/op") + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + operations_pb2.Operation(name="operations/spam") + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.update_index( + index=vectorsearch_service.Index( + dedicated_infrastructure=vectorsearch_service.DedicatedInfrastructure( + mode=vectorsearch_service.DedicatedInfrastructure.Mode.STORAGE_OPTIMIZED + ) + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + arg = args[0].index + mock_val = vectorsearch_service.Index( + dedicated_infrastructure=vectorsearch_service.DedicatedInfrastructure( + mode=vectorsearch_service.DedicatedInfrastructure.Mode.STORAGE_OPTIMIZED + ) + ) + assert arg == mock_val + arg = args[0].update_mask + mock_val = field_mask_pb2.FieldMask(paths=["paths_value"]) + assert arg == mock_val + + +@pytest.mark.asyncio +async def test_update_index_flattened_error_async(): + client = VectorSearchServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.update_index( + vectorsearch_service.UpdateIndexRequest(), + index=vectorsearch_service.Index( + dedicated_infrastructure=vectorsearch_service.DedicatedInfrastructure( + mode=vectorsearch_service.DedicatedInfrastructure.Mode.STORAGE_OPTIMIZED + ) + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + @pytest.mark.parametrize( "request_type", [ @@ -7109,6 +7468,208 @@ def test_create_index_rest_flattened_error(transport: str = "rest"): ) +def test_update_index_rest_use_cached_wrapped_rpc(): + # Clients should use _prep_wrapped_messages to create cached wrapped rpcs, + # instead of constructing them on each call + with mock.patch("google.api_core.gapic_v1.method.wrap_method") as wrapper_fn: + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Should wrap all calls on client creation + assert wrapper_fn.call_count > 0 + wrapper_fn.reset_mock() + + # Ensure method has been cached + assert client._transport.update_index in client._transport._wrapped_methods + + # Replace cached wrapped function with mock + mock_rpc = mock.Mock() + mock_rpc.return_value.name = ( + "foo" # operation_request.operation in compute client(s) expect a string. + ) + client._transport._wrapped_methods[client._transport.update_index] = mock_rpc + + request = {} + client.update_index(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + # Operation methods build a cached wrapper on first rpc call + # subsequent calls should use the cached wrapper + wrapper_fn.reset_mock() + + client.update_index(request) + + # Establish that a new wrapper was not created for this call + assert wrapper_fn.call_count == 0 + assert mock_rpc.call_count == 2 + + +def test_update_index_rest_required_fields( + request_type=vectorsearch_service.UpdateIndexRequest, +): + transport_class = transports.VectorSearchServiceRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson(pb_request, use_integers_for_enums=False) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_index._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_index._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "request_id", + "update_mask", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + req.return_value.headers = {"header-1": "value-1", "header-2": "value-2"} + + response = client.update_index(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_index_rest_unset_required_fields(): + transport = transports.VectorSearchServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_index._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "requestId", + "updateMask", + ) + ) + & set(("index",)) + ) + + +def test_update_index_rest_flattened(): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # get arguments that satisfy an http rule for this method + sample_request = { + "index": { + "name": "projects/sample1/locations/sample2/collections/sample3/indexes/sample4" + } + } + + # get truthy value for each flattened field + mock_args = dict( + index=vectorsearch_service.Index( + dedicated_infrastructure=vectorsearch_service.DedicatedInfrastructure( + mode=vectorsearch_service.DedicatedInfrastructure.Mode.STORAGE_OPTIMIZED + ) + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + req.return_value.headers = {"header-1": "value-1", "header-2": "value-2"} + + client.update_index(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v1beta/{index.name=projects/*/locations/*/collections/*/indexes/*}" + % client.transport._host, + args[1], + ) + + +def test_update_index_rest_flattened_error(transport: str = "rest"): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_index( + vectorsearch_service.UpdateIndexRequest(), + index=vectorsearch_service.Index( + dedicated_infrastructure=vectorsearch_service.DedicatedInfrastructure( + mode=vectorsearch_service.DedicatedInfrastructure.Mode.STORAGE_OPTIMIZED + ) + ), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + def test_delete_index_rest_use_cached_wrapped_rpc(): # Clients should use _prep_wrapped_messages to create cached wrapped rpcs, # instead of constructing them on each call @@ -7822,6 +8383,27 @@ def test_create_index_empty_call_grpc(): assert args[0] == request_msg +# This test is a coverage failsafe to make sure that totally empty calls, +# i.e. request == None and no flattened fields passed, work. +def test_update_index_empty_call_grpc(): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object(type(client.transport.update_index), "__call__") as call: + call.return_value = operations_pb2.Operation(name="operations/op") + client.update_index(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = vectorsearch_service.UpdateIndexRequest() + + assert args[0] == request_msg + + # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. def test_delete_index_empty_call_grpc(): @@ -8091,12 +8673,37 @@ async def test_get_index_empty_call_grpc_asyncio(): store_fields=["store_fields_value"], ) ) - await client.get_index(request=None) + await client.get_index(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = vectorsearch_service.GetIndexRequest() + + assert args[0] == request_msg + + +# This test is a coverage failsafe to make sure that totally empty calls, +# i.e. request == None and no flattened fields passed, work. +@pytest.mark.asyncio +async def test_create_index_empty_call_grpc_asyncio(): + client = VectorSearchServiceAsyncClient( + credentials=async_anonymous_credentials(), + transport="grpc_asyncio", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object(type(client.transport.create_index), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + operations_pb2.Operation(name="operations/spam") + ) + await client.create_index(request=None) # Establish that the underlying stub method was called. call.assert_called() _, args, _ = call.mock_calls[0] - request_msg = vectorsearch_service.GetIndexRequest() + request_msg = vectorsearch_service.CreateIndexRequest() assert args[0] == request_msg @@ -8104,24 +8711,24 @@ async def test_get_index_empty_call_grpc_asyncio(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @pytest.mark.asyncio -async def test_create_index_empty_call_grpc_asyncio(): +async def test_update_index_empty_call_grpc_asyncio(): client = VectorSearchServiceAsyncClient( credentials=async_anonymous_credentials(), transport="grpc_asyncio", ) # Mock the actual call, and fake the request. - with mock.patch.object(type(client.transport.create_index), "__call__") as call: + with mock.patch.object(type(client.transport.update_index), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( operations_pb2.Operation(name="operations/spam") ) - await client.create_index(request=None) + await client.update_index(request=None) # Establish that the underlying stub method was called. call.assert_called() _, args, _ = call.mock_calls[0] - request_msg = vectorsearch_service.CreateIndexRequest() + request_msg = vectorsearch_service.UpdateIndexRequest() assert args[0] == request_msg @@ -8537,6 +9144,7 @@ def test_create_collection_rest_call_success(request_type): "schema": {"fields": {}}, "vector_schema": {}, "data_schema": {}, + "encryption_spec": {"crypto_key_name": "crypto_key_name_value"}, } # The version of a generated dependency at test runtime may differ from the version used during generation. # Delete any fields which are not present in the current runtime dependency @@ -8745,6 +9353,7 @@ def test_update_collection_rest_call_success(request_type): "schema": {"fields": {}}, "vector_schema": {}, "data_schema": {}, + "encryption_spec": {"crypto_key_name": "crypto_key_name_value"}, } # The version of a generated dependency at test runtime may differ from the version used during generation. # Delete any fields which are not present in the current runtime dependency @@ -9516,6 +10125,224 @@ def test_create_index_rest_interceptors(null_interceptor): post_with_metadata.assert_called_once() +def test_update_index_rest_bad_request( + request_type=vectorsearch_service.UpdateIndexRequest, +): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + # send a request that will satisfy transcoding + request_init = { + "index": { + "name": "projects/sample1/locations/sample2/collections/sample3/indexes/sample4" + } + } + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with ( + mock.patch.object(Session, "request") as req, + pytest.raises(core_exceptions.BadRequest), + ): + # Wrap the value into a proper Response obj + response_value = mock.Mock() + json_return_value = "" + response_value.json = mock.Mock(return_value={}) + response_value.status_code = 400 + response_value.request = mock.Mock() + req.return_value = response_value + req.return_value.headers = {"header-1": "value-1", "header-2": "value-2"} + client.update_index(request) + + +@pytest.mark.parametrize( + "request_type", + [ + vectorsearch_service.UpdateIndexRequest, + dict, + ], +) +def test_update_index_rest_call_success(request_type): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + # send a request that will satisfy transcoding + request_init = { + "index": { + "name": "projects/sample1/locations/sample2/collections/sample3/indexes/sample4" + } + } + request_init["index"] = { + "dedicated_infrastructure": { + "mode": 1, + "autoscaling_spec": {"min_replica_count": 1803, "max_replica_count": 1805}, + }, + "dense_scann": {"feature_norm_type": 1}, + "name": "projects/sample1/locations/sample2/collections/sample3/indexes/sample4", + "display_name": "display_name_value", + "description": "description_value", + "labels": {}, + "create_time": {"seconds": 751, "nanos": 543}, + "update_time": {}, + "distance_metric": 1, + "index_field": "index_field_value", + "filter_fields": ["filter_fields_value1", "filter_fields_value2"], + "store_fields": ["store_fields_value1", "store_fields_value2"], + } + # The version of a generated dependency at test runtime may differ from the version used during generation. + # Delete any fields which are not present in the current runtime dependency + # See https://github.com/googleapis/gapic-generator-python/issues/1748 + + # Determine if the message type is proto-plus or protobuf + test_field = vectorsearch_service.UpdateIndexRequest.meta.fields["index"] + + def get_message_fields(field): + # Given a field which is a message (composite type), return a list with + # all the fields of the message. + # If the field is not a composite type, return an empty list. + message_fields = [] + + if hasattr(field, "message") and field.message: + is_field_type_proto_plus_type = not hasattr(field.message, "DESCRIPTOR") + + if is_field_type_proto_plus_type: + message_fields = field.message.meta.fields.values() + # Add `# pragma: NO COVER` because there may not be any `*_pb2` field types + else: # pragma: NO COVER + message_fields = field.message.DESCRIPTOR.fields + return message_fields + + runtime_nested_fields = [ + (field.name, nested_field.name) + for field in get_message_fields(test_field) + for nested_field in get_message_fields(field) + ] + + subfields_not_in_runtime = [] + + # For each item in the sample request, create a list of sub fields which are not present at runtime + # Add `# pragma: NO COVER` because this test code will not run if all subfields are present at runtime + for field, value in request_init["index"].items(): # pragma: NO COVER + result = None + is_repeated = False + # For repeated fields + if isinstance(value, list) and len(value): + is_repeated = True + result = value[0] + # For fields where the type is another message + if isinstance(value, dict): + result = value + + if result and hasattr(result, "keys"): + for subfield in result.keys(): + if (field, subfield) not in runtime_nested_fields: + subfields_not_in_runtime.append( + { + "field": field, + "subfield": subfield, + "is_repeated": is_repeated, + } + ) + + # Remove fields from the sample request which are not present in the runtime version of the dependency + # Add `# pragma: NO COVER` because this test code will not run if all subfields are present at runtime + for subfield_to_delete in subfields_not_in_runtime: # pragma: NO COVER + field = subfield_to_delete.get("field") + field_repeated = subfield_to_delete.get("is_repeated") + subfield = subfield_to_delete.get("subfield") + if subfield: + if field_repeated: + for i in range(0, len(request_init["index"][field])): + del request_init["index"][field][i][subfield] + else: + del request_init["index"][field][subfield] + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = operations_pb2.Operation(name="operations/spam") + + # Wrap the value into a proper Response obj + response_value = mock.Mock() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + response_value.content = json_return_value.encode("UTF-8") + req.return_value = response_value + req.return_value.headers = {"header-1": "value-1", "header-2": "value-2"} + response = client.update_index(request) + + # Establish that the response is the type that we expect. + json_return_value = json_format.MessageToJson(return_value) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_index_rest_interceptors(null_interceptor): + transport = transports.VectorSearchServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.VectorSearchServiceRestInterceptor(), + ) + client = VectorSearchServiceClient(transport=transport) + + with ( + mock.patch.object(type(client.transport._session), "request") as req, + mock.patch.object(path_template, "transcode") as transcode, + mock.patch.object(operation.Operation, "_set_result_from_operation"), + mock.patch.object( + transports.VectorSearchServiceRestInterceptor, "post_update_index" + ) as post, + mock.patch.object( + transports.VectorSearchServiceRestInterceptor, + "post_update_index_with_metadata", + ) as post_with_metadata, + mock.patch.object( + transports.VectorSearchServiceRestInterceptor, "pre_update_index" + ) as pre, + ): + pre.assert_not_called() + post.assert_not_called() + post_with_metadata.assert_not_called() + pb_message = vectorsearch_service.UpdateIndexRequest.pb( + vectorsearch_service.UpdateIndexRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = mock.Mock() + req.return_value.status_code = 200 + req.return_value.headers = {"header-1": "value-1", "header-2": "value-2"} + return_value = json_format.MessageToJson(operations_pb2.Operation()) + req.return_value.content = return_value + + request = vectorsearch_service.UpdateIndexRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = operations_pb2.Operation() + post_with_metadata.return_value = operations_pb2.Operation(), metadata + + client.update_index( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + post_with_metadata.assert_called_once() + + def test_delete_index_rest_bad_request( request_type=vectorsearch_service.DeleteIndexRequest, ): @@ -10445,6 +11272,26 @@ def test_create_index_empty_call_rest(): assert args[0] == request_msg +# This test is a coverage failsafe to make sure that totally empty calls, +# i.e. request == None and no flattened fields passed, work. +def test_update_index_empty_call_rest(): + client = VectorSearchServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object(type(client.transport.update_index), "__call__") as call: + client.update_index(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = vectorsearch_service.UpdateIndexRequest() + + assert args[0] == request_msg + + # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. def test_delete_index_empty_call_rest(): @@ -10567,6 +11414,7 @@ def test_vector_search_service_base_transport(): "list_indexes", "get_index", "create_index", + "update_index", "delete_index", "import_data_objects", "export_data_objects", @@ -10872,6 +11720,9 @@ def test_vector_search_service_client_transport_session_collision(transport_name session1 = client1.transport.create_index._session session2 = client2.transport.create_index._session assert session1 != session2 + session1 = client1.transport.update_index._session + session2 = client2.transport.update_index._session + assert session1 != session2 session1 = client1.transport.delete_index._session session2 = client2.transport.delete_index._session assert session1 != session2 @@ -11072,11 +11923,42 @@ def test_parse_collection_path(): assert expected == actual -def test_index_path(): +def test_crypto_key_path(): project = "cuttlefish" location = "mussel" - collection = "winkle" - index = "nautilus" + key_ring = "winkle" + crypto_key = "nautilus" + expected = "projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}".format( + project=project, + location=location, + key_ring=key_ring, + crypto_key=crypto_key, + ) + actual = VectorSearchServiceClient.crypto_key_path( + project, location, key_ring, crypto_key + ) + assert expected == actual + + +def test_parse_crypto_key_path(): + expected = { + "project": "scallop", + "location": "abalone", + "key_ring": "squid", + "crypto_key": "clam", + } + path = VectorSearchServiceClient.crypto_key_path(**expected) + + # Check that the path construction is reversible. + actual = VectorSearchServiceClient.parse_crypto_key_path(path) + assert expected == actual + + +def test_index_path(): + project = "whelk" + location = "octopus" + collection = "oyster" + index = "nudibranch" expected = "projects/{project}/locations/{location}/collections/{collection}/indexes/{index}".format( project=project, location=location, @@ -11089,10 +11971,10 @@ def test_index_path(): def test_parse_index_path(): expected = { - "project": "scallop", - "location": "abalone", - "collection": "squid", - "index": "clam", + "project": "cuttlefish", + "location": "mussel", + "collection": "winkle", + "index": "nautilus", } path = VectorSearchServiceClient.index_path(**expected) @@ -11102,7 +11984,7 @@ def test_parse_index_path(): def test_common_billing_account_path(): - billing_account = "whelk" + billing_account = "scallop" expected = "billingAccounts/{billing_account}".format( billing_account=billing_account, ) @@ -11112,7 +11994,7 @@ def test_common_billing_account_path(): def test_parse_common_billing_account_path(): expected = { - "billing_account": "octopus", + "billing_account": "abalone", } path = VectorSearchServiceClient.common_billing_account_path(**expected) @@ -11122,7 +12004,7 @@ def test_parse_common_billing_account_path(): def test_common_folder_path(): - folder = "oyster" + folder = "squid" expected = "folders/{folder}".format( folder=folder, ) @@ -11132,7 +12014,7 @@ def test_common_folder_path(): def test_parse_common_folder_path(): expected = { - "folder": "nudibranch", + "folder": "clam", } path = VectorSearchServiceClient.common_folder_path(**expected) @@ -11142,7 +12024,7 @@ def test_parse_common_folder_path(): def test_common_organization_path(): - organization = "cuttlefish" + organization = "whelk" expected = "organizations/{organization}".format( organization=organization, ) @@ -11152,7 +12034,7 @@ def test_common_organization_path(): def test_parse_common_organization_path(): expected = { - "organization": "mussel", + "organization": "octopus", } path = VectorSearchServiceClient.common_organization_path(**expected) @@ -11162,7 +12044,7 @@ def test_parse_common_organization_path(): def test_common_project_path(): - project = "winkle" + project = "oyster" expected = "projects/{project}".format( project=project, ) @@ -11172,7 +12054,7 @@ def test_common_project_path(): def test_parse_common_project_path(): expected = { - "project": "nautilus", + "project": "nudibranch", } path = VectorSearchServiceClient.common_project_path(**expected) @@ -11182,8 +12064,8 @@ def test_parse_common_project_path(): def test_common_location_path(): - project = "scallop" - location = "abalone" + project = "cuttlefish" + location = "mussel" expected = "projects/{project}/locations/{location}".format( project=project, location=location, @@ -11194,8 +12076,8 @@ def test_common_location_path(): def test_parse_common_location_path(): expected = { - "project": "squid", - "location": "clam", + "project": "winkle", + "location": "nautilus", } path = VectorSearchServiceClient.common_location_path(**expected)