diff --git a/packages/google-shopping-merchant-products/docs/index.rst b/packages/google-shopping-merchant-products/docs/index.rst index b420f11ffe56..11f4b4065747 100644 --- a/packages/google-shopping-merchant-products/docs/index.rst +++ b/packages/google-shopping-merchant-products/docs/index.rst @@ -2,6 +2,9 @@ .. include:: multiprocessing.rst +This package includes clients for multiple versions of Merchant API. +By default, you will get version ``merchant_products_v1beta``. + API Reference ------------- @@ -11,6 +14,14 @@ API Reference merchant_products_v1beta/services_ merchant_products_v1beta/types_ +API Reference +------------- +.. toctree:: + :maxdepth: 2 + + merchant_products_v1/services_ + merchant_products_v1/types_ + Changelog --------- diff --git a/packages/google-shopping-merchant-products/docs/merchant_products_v1/product_inputs_service.rst b/packages/google-shopping-merchant-products/docs/merchant_products_v1/product_inputs_service.rst new file mode 100644 index 000000000000..fd65104ee107 --- /dev/null +++ b/packages/google-shopping-merchant-products/docs/merchant_products_v1/product_inputs_service.rst @@ -0,0 +1,6 @@ +ProductInputsService +-------------------------------------- + +.. automodule:: google.shopping.merchant_products_v1.services.product_inputs_service + :members: + :inherited-members: diff --git a/packages/google-shopping-merchant-products/docs/merchant_products_v1/products_service.rst b/packages/google-shopping-merchant-products/docs/merchant_products_v1/products_service.rst new file mode 100644 index 000000000000..56dfa39c9d6b --- /dev/null +++ b/packages/google-shopping-merchant-products/docs/merchant_products_v1/products_service.rst @@ -0,0 +1,10 @@ +ProductsService +--------------------------------- + +.. automodule:: google.shopping.merchant_products_v1.services.products_service + :members: + :inherited-members: + +.. automodule:: google.shopping.merchant_products_v1.services.products_service.pagers + :members: + :inherited-members: diff --git a/packages/google-shopping-merchant-products/docs/merchant_products_v1/services_.rst b/packages/google-shopping-merchant-products/docs/merchant_products_v1/services_.rst new file mode 100644 index 000000000000..0085214f3225 --- /dev/null +++ b/packages/google-shopping-merchant-products/docs/merchant_products_v1/services_.rst @@ -0,0 +1,7 @@ +Services for Google Shopping Merchant Products v1 API +===================================================== +.. toctree:: + :maxdepth: 2 + + product_inputs_service + products_service diff --git a/packages/google-shopping-merchant-products/docs/merchant_products_v1/types_.rst b/packages/google-shopping-merchant-products/docs/merchant_products_v1/types_.rst new file mode 100644 index 000000000000..d1d1b9fe8876 --- /dev/null +++ b/packages/google-shopping-merchant-products/docs/merchant_products_v1/types_.rst @@ -0,0 +1,6 @@ +Types for Google Shopping Merchant Products v1 API +================================================== + +.. automodule:: google.shopping.merchant_products_v1.types + :members: + :show-inheritance: diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products/gapic_version.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products/gapic_version.py index 9650dd3a514f..20a9cd975b02 100644 --- a/packages/google-shopping-merchant-products/google/shopping/merchant_products/gapic_version.py +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "0.2.6" # {x-release-please-version} +__version__ = "0.0.0" # {x-release-please-version} diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/__init__.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/__init__.py new file mode 100644 index 000000000000..1b367c14b8b3 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/__init__.py @@ -0,0 +1,126 @@ +# -*- 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 google.shopping.merchant_products_v1 import gapic_version as package_version + +__version__ = package_version.__version__ + + +from .services.product_inputs_service import ( + ProductInputsServiceAsyncClient, + ProductInputsServiceClient, +) +from .services.products_service import ProductsServiceAsyncClient, ProductsServiceClient +from .types.productinputs import ( + DeleteProductInputRequest, + InsertProductInputRequest, + ProductInput, + UpdateProductInputRequest, +) +from .types.products import ( + GetProductRequest, + ListProductsRequest, + ListProductsResponse, + Product, +) +from .types.products_common import ( + AgeGroup, + AutomatedDiscounts, + Availability, + CertificationAuthority, + CertificationName, + CloudExportAdditionalProperties, + Condition, + CreditType, + DigitalSourceType, + EnergyEfficiencyClass, + FreeShippingThreshold, + Gender, + LoyaltyPoints, + LoyaltyProgram, + Pause, + PickupMethod, + PickupSla, + ProductAttributes, + ProductCertification, + ProductDetail, + ProductDimension, + ProductInstallment, + ProductStatus, + ProductSustainabilityIncentive, + ProductWeight, + Shipping, + ShippingDimension, + ShippingWeight, + SizeSystem, + SizeType, + StructuredDescription, + StructuredTitle, + SubscriptionCost, + SubscriptionPeriod, + UnitPricingBaseMeasure, + UnitPricingMeasure, +) + +__all__ = ( + "ProductInputsServiceAsyncClient", + "ProductsServiceAsyncClient", + "AgeGroup", + "AutomatedDiscounts", + "Availability", + "CertificationAuthority", + "CertificationName", + "CloudExportAdditionalProperties", + "Condition", + "CreditType", + "DeleteProductInputRequest", + "DigitalSourceType", + "EnergyEfficiencyClass", + "FreeShippingThreshold", + "Gender", + "GetProductRequest", + "InsertProductInputRequest", + "ListProductsRequest", + "ListProductsResponse", + "LoyaltyPoints", + "LoyaltyProgram", + "Pause", + "PickupMethod", + "PickupSla", + "Product", + "ProductAttributes", + "ProductCertification", + "ProductDetail", + "ProductDimension", + "ProductInput", + "ProductInputsServiceClient", + "ProductInstallment", + "ProductStatus", + "ProductSustainabilityIncentive", + "ProductWeight", + "ProductsServiceClient", + "Shipping", + "ShippingDimension", + "ShippingWeight", + "SizeSystem", + "SizeType", + "StructuredDescription", + "StructuredTitle", + "SubscriptionCost", + "SubscriptionPeriod", + "UnitPricingBaseMeasure", + "UnitPricingMeasure", + "UpdateProductInputRequest", +) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/gapic_metadata.json b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/gapic_metadata.json new file mode 100644 index 000000000000..7df4ba981da9 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/gapic_metadata.json @@ -0,0 +1,122 @@ + { + "comment": "This file maps proto services/RPCs to the corresponding library clients/methods", + "language": "python", + "libraryPackage": "google.shopping.merchant_products_v1", + "protoPackage": "google.shopping.merchant.products.v1", + "schema": "1.0", + "services": { + "ProductInputsService": { + "clients": { + "grpc": { + "libraryClient": "ProductInputsServiceClient", + "rpcs": { + "DeleteProductInput": { + "methods": [ + "delete_product_input" + ] + }, + "InsertProductInput": { + "methods": [ + "insert_product_input" + ] + }, + "UpdateProductInput": { + "methods": [ + "update_product_input" + ] + } + } + }, + "grpc-async": { + "libraryClient": "ProductInputsServiceAsyncClient", + "rpcs": { + "DeleteProductInput": { + "methods": [ + "delete_product_input" + ] + }, + "InsertProductInput": { + "methods": [ + "insert_product_input" + ] + }, + "UpdateProductInput": { + "methods": [ + "update_product_input" + ] + } + } + }, + "rest": { + "libraryClient": "ProductInputsServiceClient", + "rpcs": { + "DeleteProductInput": { + "methods": [ + "delete_product_input" + ] + }, + "InsertProductInput": { + "methods": [ + "insert_product_input" + ] + }, + "UpdateProductInput": { + "methods": [ + "update_product_input" + ] + } + } + } + } + }, + "ProductsService": { + "clients": { + "grpc": { + "libraryClient": "ProductsServiceClient", + "rpcs": { + "GetProduct": { + "methods": [ + "get_product" + ] + }, + "ListProducts": { + "methods": [ + "list_products" + ] + } + } + }, + "grpc-async": { + "libraryClient": "ProductsServiceAsyncClient", + "rpcs": { + "GetProduct": { + "methods": [ + "get_product" + ] + }, + "ListProducts": { + "methods": [ + "list_products" + ] + } + } + }, + "rest": { + "libraryClient": "ProductsServiceClient", + "rpcs": { + "GetProduct": { + "methods": [ + "get_product" + ] + }, + "ListProducts": { + "methods": [ + "list_products" + ] + } + } + } + } + } + } +} diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/gapic_version.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/gapic_version.py new file mode 100644 index 000000000000..20a9cd975b02 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/gapic_version.py @@ -0,0 +1,16 @@ +# -*- 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. +# +__version__ = "0.0.0" # {x-release-please-version} diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/py.typed b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/py.typed new file mode 100644 index 000000000000..962817aecdcd --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/py.typed @@ -0,0 +1,2 @@ +# Marker file for PEP 561. +# The google-shopping-merchant-products package uses inline types. diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/__init__.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/__init__.py new file mode 100644 index 000000000000..cbf94b283c70 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/__init__.py @@ -0,0 +1,15 @@ +# -*- 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. +# diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/__init__.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/__init__.py new file mode 100644 index 000000000000..00604516d58f --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/__init__.py @@ -0,0 +1,22 @@ +# -*- 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 .async_client import ProductInputsServiceAsyncClient +from .client import ProductInputsServiceClient + +__all__ = ( + "ProductInputsServiceClient", + "ProductInputsServiceAsyncClient", +) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/async_client.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/async_client.py new file mode 100644 index 000000000000..9ad2a02f221b --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/async_client.py @@ -0,0 +1,749 @@ +# -*- 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 collections import OrderedDict +import logging as std_logging +import re +from typing import ( + Callable, + Dict, + Mapping, + MutableMapping, + MutableSequence, + Optional, + Sequence, + Tuple, + Type, + Union, +) + +from google.api_core import exceptions as core_exceptions +from google.api_core import gapic_v1 +from google.api_core import retry_async as retries +from google.api_core.client_options import ClientOptions +from google.auth import credentials as ga_credentials # type: ignore +from google.oauth2 import service_account # type: ignore +import google.protobuf + +from google.shopping.merchant_products_v1 import gapic_version as package_version + +try: + OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.AsyncRetry, object, None] # type: ignore + +from google.protobuf import field_mask_pb2 # type: ignore +from google.shopping.type.types import types + +from google.shopping.merchant_products_v1.types import productinputs, products_common + +from .client import ProductInputsServiceClient +from .transports.base import DEFAULT_CLIENT_INFO, ProductInputsServiceTransport +from .transports.grpc_asyncio import ProductInputsServiceGrpcAsyncIOTransport + +try: + from google.api_core import client_logging # type: ignore + + CLIENT_LOGGING_SUPPORTED = True # pragma: NO COVER +except ImportError: # pragma: NO COVER + CLIENT_LOGGING_SUPPORTED = False + +_LOGGER = std_logging.getLogger(__name__) + + +class ProductInputsServiceAsyncClient: + """Service to use ProductInput resource.""" + + _client: ProductInputsServiceClient + + # Copy defaults from the synchronous client for use here. + # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead. + DEFAULT_ENDPOINT = ProductInputsServiceClient.DEFAULT_ENDPOINT + DEFAULT_MTLS_ENDPOINT = ProductInputsServiceClient.DEFAULT_MTLS_ENDPOINT + _DEFAULT_ENDPOINT_TEMPLATE = ProductInputsServiceClient._DEFAULT_ENDPOINT_TEMPLATE + _DEFAULT_UNIVERSE = ProductInputsServiceClient._DEFAULT_UNIVERSE + + product_path = staticmethod(ProductInputsServiceClient.product_path) + parse_product_path = staticmethod(ProductInputsServiceClient.parse_product_path) + product_input_path = staticmethod(ProductInputsServiceClient.product_input_path) + parse_product_input_path = staticmethod( + ProductInputsServiceClient.parse_product_input_path + ) + common_billing_account_path = staticmethod( + ProductInputsServiceClient.common_billing_account_path + ) + parse_common_billing_account_path = staticmethod( + ProductInputsServiceClient.parse_common_billing_account_path + ) + common_folder_path = staticmethod(ProductInputsServiceClient.common_folder_path) + parse_common_folder_path = staticmethod( + ProductInputsServiceClient.parse_common_folder_path + ) + common_organization_path = staticmethod( + ProductInputsServiceClient.common_organization_path + ) + parse_common_organization_path = staticmethod( + ProductInputsServiceClient.parse_common_organization_path + ) + common_project_path = staticmethod(ProductInputsServiceClient.common_project_path) + parse_common_project_path = staticmethod( + ProductInputsServiceClient.parse_common_project_path + ) + common_location_path = staticmethod(ProductInputsServiceClient.common_location_path) + parse_common_location_path = staticmethod( + ProductInputsServiceClient.parse_common_location_path + ) + + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials + info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + ProductInputsServiceAsyncClient: The constructed client. + """ + return ProductInputsServiceClient.from_service_account_info.__func__(ProductInputsServiceAsyncClient, info, *args, **kwargs) # type: ignore + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + ProductInputsServiceAsyncClient: The constructed client. + """ + return ProductInputsServiceClient.from_service_account_file.__func__(ProductInputsServiceAsyncClient, filename, *args, **kwargs) # type: ignore + + from_service_account_json = from_service_account_file + + @classmethod + def get_mtls_endpoint_and_cert_source( + cls, client_options: Optional[ClientOptions] = None + ): + """Return the API endpoint and client cert source for mutual TLS. + + The client cert source is determined in the following order: + (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the + client cert source is None. + (2) if `client_options.client_cert_source` is provided, use the provided one; if the + default client cert source exists, use the default one; otherwise the client cert + source is None. + + The API endpoint is determined in the following order: + (1) if `client_options.api_endpoint` if provided, use the provided one. + (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the + default mTLS endpoint; if the environment variable is "never", use the default API + endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise + use the default API endpoint. + + More details can be found at https://google.aip.dev/auth/4114. + + Args: + client_options (google.api_core.client_options.ClientOptions): Custom options for the + client. Only the `api_endpoint` and `client_cert_source` properties may be used + in this method. + + Returns: + Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the + client cert source to use. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If any errors happen. + """ + return ProductInputsServiceClient.get_mtls_endpoint_and_cert_source(client_options) # type: ignore + + @property + def transport(self) -> ProductInputsServiceTransport: + """Returns the transport used by the client instance. + + Returns: + ProductInputsServiceTransport: The transport used by the client instance. + """ + return self._client.transport + + @property + def api_endpoint(self): + """Return the API endpoint used by the client instance. + + Returns: + str: The API endpoint used by the client instance. + """ + return self._client._api_endpoint + + @property + def universe_domain(self) -> str: + """Return the universe domain used by the client instance. + + Returns: + str: The universe domain used + by the client instance. + """ + return self._client._universe_domain + + get_transport_class = ProductInputsServiceClient.get_transport_class + + def __init__( + self, + *, + credentials: Optional[ga_credentials.Credentials] = None, + transport: Optional[ + Union[ + str, + ProductInputsServiceTransport, + Callable[..., ProductInputsServiceTransport], + ] + ] = "grpc_asyncio", + client_options: Optional[ClientOptions] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiates the product inputs service async client. + + Args: + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + transport (Optional[Union[str,ProductInputsServiceTransport,Callable[..., ProductInputsServiceTransport]]]): + The transport to use, or a Callable that constructs and returns a new transport to use. + If a Callable is given, it will be called with the same set of initialization + arguments as used in the ProductInputsServiceTransport constructor. + If set to None, a transport is chosen automatically. + client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]): + Custom options for the client. + + 1. The ``api_endpoint`` property can be used to override the + default endpoint provided by the client when ``transport`` is + not explicitly provided. Only if this property is not set and + ``transport`` was not explicitly provided, the endpoint is + determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment + variable, which have one of the following values: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint) and "auto" (auto-switch to the + default mTLS endpoint if client certificate is present; this is + the default value). + + 2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide a client certificate for mTLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + + 3. The ``universe_domain`` property can be used to override the + default "googleapis.com" universe. Note that ``api_endpoint`` + property still takes precedence; and ``universe_domain`` is + currently not supported for mTLS. + + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + """ + self._client = ProductInputsServiceClient( + credentials=credentials, + transport=transport, + client_options=client_options, + client_info=client_info, + ) + + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + std_logging.DEBUG + ): # pragma: NO COVER + _LOGGER.debug( + "Created client `google.shopping.merchant.products_v1.ProductInputsServiceAsyncClient`.", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductInputsService", + "universeDomain": getattr( + self._client._transport._credentials, "universe_domain", "" + ), + "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}", + "credentialsInfo": getattr( + self.transport._credentials, "get_cred_info", lambda: None + )(), + } + if hasattr(self._client._transport, "_credentials") + else { + "serviceName": "google.shopping.merchant.products.v1.ProductInputsService", + "credentialsType": None, + }, + ) + + async def insert_product_input( + self, + request: Optional[Union[productinputs.InsertProductInputRequest, dict]] = None, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> productinputs.ProductInput: + r"""`Uploads a product input to your Merchant Center + account `__. + You must have a products `data + source `__ to be + able to insert a product. The unique identifier of the data + source is passed as a query parameter in the request URL. + + If a product input with the same contentLanguage, offerId, and + dataSource already exists, then the product input inserted by + this method replaces that entry. + + After inserting, updating, or deleting a product input, it may + take several minutes before the processed product can be + retrieved. + + .. 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.shopping import merchant_products_v1 + + async def sample_insert_product_input(): + # Create a client + client = merchant_products_v1.ProductInputsServiceAsyncClient() + + # Initialize request argument(s) + product_input = merchant_products_v1.ProductInput() + product_input.offer_id = "offer_id_value" + product_input.content_language = "content_language_value" + product_input.feed_label = "feed_label_value" + + request = merchant_products_v1.InsertProductInputRequest( + parent="parent_value", + product_input=product_input, + data_source="data_source_value", + ) + + # Make the request + response = await client.insert_product_input(request=request) + + # Handle the response + print(response) + + Args: + request (Optional[Union[google.shopping.merchant_products_v1.types.InsertProductInputRequest, dict]]): + The request object. Request message for the + InsertProductInput method. + 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.shopping.merchant_products_v1.types.ProductInput: + This resource represents input data you submit for a product, not the + processed product that you see in Merchant Center, in + Shopping ads, or across Google surfaces. Product + inputs, rules and supplemental data source data are + combined to create the processed + [Product][google.shopping.merchant.products.v1.Product]. + For more information, see [Manage + products](/merchant/api/guides/products/overview). + + Required product input attributes to pass data + validation checks are primarily defined in the + [Products Data + Specification](\ https://support.google.com/merchants/answer/188494). + + The following attributes are required: + [feedLabel][google.shopping.merchant.products.v1.Product.feed_label], + [contentLanguage][google.shopping.merchant.products.v1.Product.content_language] + and + [offerId][google.shopping.merchant.products.v1.Product.offer_id]. + + After inserting, updating, or deleting a product + input, it may take several minutes before the + processed product can be retrieved. + + All fields in the product input and its sub-messages + match the English name of their corresponding + attribute in the [Products Data + Specification](\ https://support.google.com/merchants/answer/188494) + with [some + exceptions](\ https://support.google.com/merchants/answer/7052112). + The following reference documentation lists the field + names in the **camelCase** casing style while the + Products Data Specification lists the names in the + **snake_case** casing style. + + """ + # Create or coerce a protobuf request object. + # - 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, productinputs.InsertProductInputRequest): + request = productinputs.InsertProductInputRequest(request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._client._transport._wrapped_methods[ + self._client._transport.insert_product_input + ] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Validate the universe domain. + self._client._validate_universe_domain() + + # Send the request. + response = await rpc( + request, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Done; return the response. + return response + + async def update_product_input( + self, + request: Optional[Union[productinputs.UpdateProductInputRequest, dict]] = None, + *, + product_input: Optional[productinputs.ProductInput] = 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]]] = (), + ) -> productinputs.ProductInput: + r"""Updates the existing product input in your Merchant + Center account. + After inserting, updating, or deleting a product input, + it may take several minutes before the processed product + can be retrieved. + + .. 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.shopping import merchant_products_v1 + + async def sample_update_product_input(): + # Create a client + client = merchant_products_v1.ProductInputsServiceAsyncClient() + + # Initialize request argument(s) + product_input = merchant_products_v1.ProductInput() + product_input.offer_id = "offer_id_value" + product_input.content_language = "content_language_value" + product_input.feed_label = "feed_label_value" + + request = merchant_products_v1.UpdateProductInputRequest( + product_input=product_input, + data_source="data_source_value", + ) + + # Make the request + response = await client.update_product_input(request=request) + + # Handle the response + print(response) + + Args: + request (Optional[Union[google.shopping.merchant_products_v1.types.UpdateProductInputRequest, dict]]): + The request object. Request message for the + UpdateProductInput method. The product + (primary input) must exist for the + update to succeed. If the update is for + a primary product input, the existing + primary product input must be from the + same data source. + product_input (:class:`google.shopping.merchant_products_v1.types.ProductInput`): + Required. The product input resource + to update. Information you submit will + be applied to the processed product as + well. + + This corresponds to the ``product_input`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + update_mask (:class:`google.protobuf.field_mask_pb2.FieldMask`): + Optional. The list of product attributes to be updated. + + If the update mask is omitted, then it is treated as + implied field mask equivalent to all fields that are + populated (have a non-empty value). + + Attributes specified in the update mask without a value + specified in the body will be deleted from the product. + + Update mask can only be specified for top level fields + in attributes and custom attributes. + + To specify the update mask for custom attributes you + need to add the ``custom_attribute.`` prefix. + + Providing special "*" value for full product replacement + is not supported. + + 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.shopping.merchant_products_v1.types.ProductInput: + This resource represents input data you submit for a product, not the + processed product that you see in Merchant Center, in + Shopping ads, or across Google surfaces. Product + inputs, rules and supplemental data source data are + combined to create the processed + [Product][google.shopping.merchant.products.v1.Product]. + For more information, see [Manage + products](/merchant/api/guides/products/overview). + + Required product input attributes to pass data + validation checks are primarily defined in the + [Products Data + Specification](\ https://support.google.com/merchants/answer/188494). + + The following attributes are required: + [feedLabel][google.shopping.merchant.products.v1.Product.feed_label], + [contentLanguage][google.shopping.merchant.products.v1.Product.content_language] + and + [offerId][google.shopping.merchant.products.v1.Product.offer_id]. + + After inserting, updating, or deleting a product + input, it may take several minutes before the + processed product can be retrieved. + + All fields in the product input and its sub-messages + match the English name of their corresponding + attribute in the [Products Data + Specification](\ https://support.google.com/merchants/answer/188494) + with [some + exceptions](\ https://support.google.com/merchants/answer/7052112). + The following reference documentation lists the field + names in the **camelCase** casing style while the + Products Data Specification lists the names in the + **snake_case** casing style. + + """ + # 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 = [product_input, 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, productinputs.UpdateProductInputRequest): + request = productinputs.UpdateProductInputRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + if product_input is not None: + request.product_input = product_input + 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_product_input + ] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata( + (("product_input.name", request.product_input.name),) + ), + ) + + # Validate the universe domain. + self._client._validate_universe_domain() + + # Send the request. + response = await rpc( + request, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Done; return the response. + return response + + async def delete_product_input( + self, + request: Optional[Union[productinputs.DeleteProductInputRequest, dict]] = None, + *, + name: Optional[str] = None, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> None: + r"""Deletes a product input from your Merchant Center + account. + After inserting, updating, or deleting a product input, + it may take several minutes before the processed product + can be retrieved. + + .. 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.shopping import merchant_products_v1 + + async def sample_delete_product_input(): + # Create a client + client = merchant_products_v1.ProductInputsServiceAsyncClient() + + # Initialize request argument(s) + request = merchant_products_v1.DeleteProductInputRequest( + name="name_value", + data_source="data_source_value", + ) + + # Make the request + await client.delete_product_input(request=request) + + Args: + request (Optional[Union[google.shopping.merchant_products_v1.types.DeleteProductInputRequest, dict]]): + The request object. Request message for the + DeleteProductInput method. + name (:class:`str`): + Required. The name of the product input resource to + delete. Format: + ``accounts/{account}/productInputs/{product}`` where the + last section ``product`` consists of: + ``content_language~feed_label~offer_id`` example for + product name is + ``accounts/123/productInputs/en~US~sku123``. + + This corresponds to the ``name`` 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`. + """ + # 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 = [name] + 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, productinputs.DeleteProductInputRequest): + request = productinputs.DeleteProductInputRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._client._transport._wrapped_methods[ + self._client._transport.delete_product_input + ] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Validate the universe domain. + self._client._validate_universe_domain() + + # Send the request. + await rpc( + request, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + async def __aenter__(self) -> "ProductInputsServiceAsyncClient": + return self + + async def __aexit__(self, exc_type, exc, tb): + await self.transport.close() + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=package_version.__version__ +) + +if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"): # pragma: NO COVER + DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__ + + +__all__ = ("ProductInputsServiceAsyncClient",) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/client.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/client.py new file mode 100644 index 000000000000..017011c2f6ee --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/client.py @@ -0,0 +1,1181 @@ +# -*- 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 collections import OrderedDict +from http import HTTPStatus +import json +import logging as std_logging +import os +import re +from typing import ( + Callable, + Dict, + Mapping, + MutableMapping, + MutableSequence, + Optional, + Sequence, + Tuple, + Type, + Union, + cast, +) +import warnings + +from google.api_core import client_options as client_options_lib +from google.api_core import exceptions as core_exceptions +from google.api_core import gapic_v1 +from google.api_core import retry as retries +from google.auth import credentials as ga_credentials # type: ignore +from google.auth.exceptions import MutualTLSChannelError # type: ignore +from google.auth.transport import mtls # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.oauth2 import service_account # type: ignore +import google.protobuf + +from google.shopping.merchant_products_v1 import gapic_version as package_version + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object, None] # type: ignore + +try: + from google.api_core import client_logging # type: ignore + + CLIENT_LOGGING_SUPPORTED = True # pragma: NO COVER +except ImportError: # pragma: NO COVER + CLIENT_LOGGING_SUPPORTED = False + +_LOGGER = std_logging.getLogger(__name__) + +from google.protobuf import field_mask_pb2 # type: ignore +from google.shopping.type.types import types + +from google.shopping.merchant_products_v1.types import productinputs, products_common + +from .transports.base import DEFAULT_CLIENT_INFO, ProductInputsServiceTransport +from .transports.grpc import ProductInputsServiceGrpcTransport +from .transports.grpc_asyncio import ProductInputsServiceGrpcAsyncIOTransport +from .transports.rest import ProductInputsServiceRestTransport + + +class ProductInputsServiceClientMeta(type): + """Metaclass for the ProductInputsService client. + + This provides class-level methods for building and retrieving + support objects (e.g. transport) without polluting the client instance + objects. + """ + + _transport_registry = ( + OrderedDict() + ) # type: Dict[str, Type[ProductInputsServiceTransport]] + _transport_registry["grpc"] = ProductInputsServiceGrpcTransport + _transport_registry["grpc_asyncio"] = ProductInputsServiceGrpcAsyncIOTransport + _transport_registry["rest"] = ProductInputsServiceRestTransport + + def get_transport_class( + cls, + label: Optional[str] = None, + ) -> Type[ProductInputsServiceTransport]: + """Returns an appropriate transport class. + + Args: + label: The name of the desired transport. If none is + provided, then the first transport in the registry is used. + + Returns: + The transport class to use. + """ + # If a specific transport is requested, return that one. + if label: + return cls._transport_registry[label] + + # No transport is requested; return the default (that is, the first one + # in the dictionary). + return next(iter(cls._transport_registry.values())) + + +class ProductInputsServiceClient(metaclass=ProductInputsServiceClientMeta): + """Service to use ProductInput resource.""" + + @staticmethod + def _get_default_mtls_endpoint(api_endpoint): + """Converts api endpoint to mTLS endpoint. + + Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to + "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively. + Args: + api_endpoint (Optional[str]): the api endpoint to convert. + Returns: + str: converted mTLS api endpoint. + """ + if not api_endpoint: + return api_endpoint + + mtls_endpoint_re = re.compile( + r"(?P[^.]+)(?P\.mtls)?(?P\.sandbox)?(?P\.googleapis\.com)?" + ) + + m = mtls_endpoint_re.match(api_endpoint) + name, mtls, sandbox, googledomain = m.groups() + if mtls or not googledomain: + return api_endpoint + + if sandbox: + return api_endpoint.replace( + "sandbox.googleapis.com", "mtls.sandbox.googleapis.com" + ) + + return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com") + + # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead. + DEFAULT_ENDPOINT = "merchantapi.googleapis.com" + DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__( # type: ignore + DEFAULT_ENDPOINT + ) + + _DEFAULT_ENDPOINT_TEMPLATE = "merchantapi.{UNIVERSE_DOMAIN}" + _DEFAULT_UNIVERSE = "googleapis.com" + + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials + info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + ProductInputsServiceClient: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_info(info) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + ProductInputsServiceClient: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_file(filename) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + + from_service_account_json = from_service_account_file + + @property + def transport(self) -> ProductInputsServiceTransport: + """Returns the transport used by the client instance. + + Returns: + ProductInputsServiceTransport: The transport used by the client + instance. + """ + return self._transport + + @staticmethod + def product_path( + account: str, + product: str, + ) -> str: + """Returns a fully-qualified product string.""" + return "accounts/{account}/products/{product}".format( + account=account, + product=product, + ) + + @staticmethod + def parse_product_path(path: str) -> Dict[str, str]: + """Parses a product path into its component segments.""" + m = re.match(r"^accounts/(?P.+?)/products/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def product_input_path( + account: str, + productinput: str, + ) -> str: + """Returns a fully-qualified product_input string.""" + return "accounts/{account}/productInputs/{productinput}".format( + account=account, + productinput=productinput, + ) + + @staticmethod + def parse_product_input_path(path: str) -> Dict[str, str]: + """Parses a product_input path into its component segments.""" + m = re.match( + r"^accounts/(?P.+?)/productInputs/(?P.+?)$", path + ) + return m.groupdict() if m else {} + + @staticmethod + def common_billing_account_path( + billing_account: str, + ) -> str: + """Returns a fully-qualified billing_account string.""" + return "billingAccounts/{billing_account}".format( + billing_account=billing_account, + ) + + @staticmethod + def parse_common_billing_account_path(path: str) -> Dict[str, str]: + """Parse a billing_account path into its component segments.""" + m = re.match(r"^billingAccounts/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_folder_path( + folder: str, + ) -> str: + """Returns a fully-qualified folder string.""" + return "folders/{folder}".format( + folder=folder, + ) + + @staticmethod + def parse_common_folder_path(path: str) -> Dict[str, str]: + """Parse a folder path into its component segments.""" + m = re.match(r"^folders/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_organization_path( + organization: str, + ) -> str: + """Returns a fully-qualified organization string.""" + return "organizations/{organization}".format( + organization=organization, + ) + + @staticmethod + def parse_common_organization_path(path: str) -> Dict[str, str]: + """Parse a organization path into its component segments.""" + m = re.match(r"^organizations/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_project_path( + project: str, + ) -> str: + """Returns a fully-qualified project string.""" + return "projects/{project}".format( + project=project, + ) + + @staticmethod + def parse_common_project_path(path: str) -> Dict[str, str]: + """Parse a project path into its component segments.""" + m = re.match(r"^projects/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_location_path( + project: str, + location: str, + ) -> str: + """Returns a fully-qualified location string.""" + return "projects/{project}/locations/{location}".format( + project=project, + location=location, + ) + + @staticmethod + def parse_common_location_path(path: str) -> Dict[str, str]: + """Parse a location path into its component segments.""" + m = re.match(r"^projects/(?P.+?)/locations/(?P.+?)$", path) + return m.groupdict() if m else {} + + @classmethod + def get_mtls_endpoint_and_cert_source( + cls, client_options: Optional[client_options_lib.ClientOptions] = None + ): + """Deprecated. Return the API endpoint and client cert source for mutual TLS. + + The client cert source is determined in the following order: + (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the + client cert source is None. + (2) if `client_options.client_cert_source` is provided, use the provided one; if the + default client cert source exists, use the default one; otherwise the client cert + source is None. + + The API endpoint is determined in the following order: + (1) if `client_options.api_endpoint` if provided, use the provided one. + (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the + default mTLS endpoint; if the environment variable is "never", use the default API + endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise + use the default API endpoint. + + More details can be found at https://google.aip.dev/auth/4114. + + Args: + client_options (google.api_core.client_options.ClientOptions): Custom options for the + client. Only the `api_endpoint` and `client_cert_source` properties may be used + in this method. + + Returns: + Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the + client cert source to use. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If any errors happen. + """ + + warnings.warn( + "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.", + DeprecationWarning, + ) + if client_options is None: + client_options = client_options_lib.ClientOptions() + use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") + if use_client_cert not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) + if use_mtls_endpoint not in ("auto", "never", "always"): + raise MutualTLSChannelError( + "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" + ) + + # Figure out the client cert source to use. + client_cert_source = None + if use_client_cert == "true": + if client_options.client_cert_source: + client_cert_source = client_options.client_cert_source + elif mtls.has_default_client_cert_source(): + client_cert_source = mtls.default_client_cert_source() + + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + elif use_mtls_endpoint == "always" or ( + use_mtls_endpoint == "auto" and client_cert_source + ): + api_endpoint = cls.DEFAULT_MTLS_ENDPOINT + else: + api_endpoint = cls.DEFAULT_ENDPOINT + + return api_endpoint, client_cert_source + + @staticmethod + def _read_environment_variables(): + """Returns the environment variables used by the client. + + Returns: + Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE, + GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables. + + Raises: + ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not + any of ["true", "false"]. + google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT + is not any of ["auto", "never", "always"]. + """ + use_client_cert = os.getenv( + "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false" + ).lower() + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() + universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") + if use_client_cert not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) + if use_mtls_endpoint not in ("auto", "never", "always"): + raise MutualTLSChannelError( + "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" + ) + return use_client_cert == "true", use_mtls_endpoint, universe_domain_env + + @staticmethod + def _get_client_cert_source(provided_cert_source, use_cert_flag): + """Return the client cert source to be used by the client. + + Args: + provided_cert_source (bytes): The client certificate source provided. + use_cert_flag (bool): A flag indicating whether to use the client certificate. + + Returns: + bytes or None: The client cert source to be used by the client. + """ + client_cert_source = None + if use_cert_flag: + if provided_cert_source: + client_cert_source = provided_cert_source + elif mtls.has_default_client_cert_source(): + client_cert_source = mtls.default_client_cert_source() + return client_cert_source + + @staticmethod + def _get_api_endpoint( + api_override, client_cert_source, universe_domain, use_mtls_endpoint + ): + """Return the API endpoint used by the client. + + Args: + api_override (str): The API endpoint override. If specified, this is always + the return value of this function and the other arguments are not used. + client_cert_source (bytes): The client certificate source used by the client. + universe_domain (str): The universe domain used by the client. + use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters. + Possible values are "always", "auto", or "never". + + Returns: + str: The API endpoint to be used by the client. + """ + if api_override is not None: + api_endpoint = api_override + elif use_mtls_endpoint == "always" or ( + use_mtls_endpoint == "auto" and client_cert_source + ): + _default_universe = ProductInputsServiceClient._DEFAULT_UNIVERSE + if universe_domain != _default_universe: + raise MutualTLSChannelError( + f"mTLS is not supported in any universe other than {_default_universe}." + ) + api_endpoint = ProductInputsServiceClient.DEFAULT_MTLS_ENDPOINT + else: + api_endpoint = ProductInputsServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=universe_domain + ) + return api_endpoint + + @staticmethod + def _get_universe_domain( + client_universe_domain: Optional[str], universe_domain_env: Optional[str] + ) -> str: + """Return the universe domain used by the client. + + Args: + client_universe_domain (Optional[str]): The universe domain configured via the client options. + universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable. + + Returns: + str: The universe domain to be used by the client. + + Raises: + ValueError: If the universe domain is an empty string. + """ + universe_domain = ProductInputsServiceClient._DEFAULT_UNIVERSE + if client_universe_domain is not None: + universe_domain = client_universe_domain + elif universe_domain_env is not None: + universe_domain = universe_domain_env + if len(universe_domain.strip()) == 0: + raise ValueError("Universe Domain cannot be an empty string.") + return universe_domain + + def _validate_universe_domain(self): + """Validates client's and credentials' universe domains are consistent. + + Returns: + bool: True iff the configured universe domain is valid. + + Raises: + ValueError: If the configured universe domain is not valid. + """ + + # NOTE (b/349488459): universe validation is disabled until further notice. + return True + + def _add_cred_info_for_auth_errors( + self, error: core_exceptions.GoogleAPICallError + ) -> None: + """Adds credential info string to error details for 401/403/404 errors. + + Args: + error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info. + """ + if error.code not in [ + HTTPStatus.UNAUTHORIZED, + HTTPStatus.FORBIDDEN, + HTTPStatus.NOT_FOUND, + ]: + return + + cred = self._transport._credentials + + # get_cred_info is only available in google-auth>=2.35.0 + if not hasattr(cred, "get_cred_info"): + return + + # ignore the type check since pypy test fails when get_cred_info + # is not available + cred_info = cred.get_cred_info() # type: ignore + if cred_info and hasattr(error._details, "append"): + error._details.append(json.dumps(cred_info)) + + @property + def api_endpoint(self): + """Return the API endpoint used by the client instance. + + Returns: + str: The API endpoint used by the client instance. + """ + return self._api_endpoint + + @property + def universe_domain(self) -> str: + """Return the universe domain used by the client instance. + + Returns: + str: The universe domain used by the client instance. + """ + return self._universe_domain + + def __init__( + self, + *, + credentials: Optional[ga_credentials.Credentials] = None, + transport: Optional[ + Union[ + str, + ProductInputsServiceTransport, + Callable[..., ProductInputsServiceTransport], + ] + ] = None, + client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiates the product inputs service client. + + Args: + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + transport (Optional[Union[str,ProductInputsServiceTransport,Callable[..., ProductInputsServiceTransport]]]): + The transport to use, or a Callable that constructs and returns a new transport. + If a Callable is given, it will be called with the same set of initialization + arguments as used in the ProductInputsServiceTransport constructor. + If set to None, a transport is chosen automatically. + client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]): + Custom options for the client. + + 1. The ``api_endpoint`` property can be used to override the + default endpoint provided by the client when ``transport`` is + not explicitly provided. Only if this property is not set and + ``transport`` was not explicitly provided, the endpoint is + determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment + variable, which have one of the following values: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint) and "auto" (auto-switch to the + default mTLS endpoint if client certificate is present; this is + the default value). + + 2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide a client certificate for mTLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + + 3. The ``universe_domain`` property can be used to override the + default "googleapis.com" universe. Note that the ``api_endpoint`` + property still takes precedence; and ``universe_domain`` is + currently not supported for mTLS. + + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport + creation failed for any reason. + """ + self._client_options = client_options + if isinstance(self._client_options, dict): + self._client_options = client_options_lib.from_dict(self._client_options) + if self._client_options is None: + self._client_options = client_options_lib.ClientOptions() + self._client_options = cast( + client_options_lib.ClientOptions, self._client_options + ) + + universe_domain_opt = getattr(self._client_options, "universe_domain", None) + + ( + self._use_client_cert, + self._use_mtls_endpoint, + self._universe_domain_env, + ) = ProductInputsServiceClient._read_environment_variables() + self._client_cert_source = ProductInputsServiceClient._get_client_cert_source( + self._client_options.client_cert_source, self._use_client_cert + ) + self._universe_domain = ProductInputsServiceClient._get_universe_domain( + universe_domain_opt, self._universe_domain_env + ) + self._api_endpoint = None # updated below, depending on `transport` + + # Initialize the universe domain validation. + self._is_universe_domain_valid = False + + if CLIENT_LOGGING_SUPPORTED: # pragma: NO COVER + # Setup logging. + client_logging.initialize_logging() + + api_key_value = getattr(self._client_options, "api_key", None) + if api_key_value and credentials: + raise ValueError( + "client_options.api_key and credentials are mutually exclusive" + ) + + # Save or instantiate the transport. + # Ordinarily, we provide the transport, but allowing a custom transport + # instance provides an extensibility point for unusual situations. + transport_provided = isinstance(transport, ProductInputsServiceTransport) + if transport_provided: + # transport is a ProductInputsServiceTransport instance. + if credentials or self._client_options.credentials_file or api_key_value: + raise ValueError( + "When providing a transport instance, " + "provide its credentials directly." + ) + if self._client_options.scopes: + raise ValueError( + "When providing a transport instance, provide its scopes " + "directly." + ) + self._transport = cast(ProductInputsServiceTransport, transport) + self._api_endpoint = self._transport.host + + self._api_endpoint = ( + self._api_endpoint + or ProductInputsServiceClient._get_api_endpoint( + self._client_options.api_endpoint, + self._client_cert_source, + self._universe_domain, + self._use_mtls_endpoint, + ) + ) + + if not transport_provided: + import google.auth._default # type: ignore + + if api_key_value and hasattr( + google.auth._default, "get_api_key_credentials" + ): + credentials = google.auth._default.get_api_key_credentials( + api_key_value + ) + + transport_init: Union[ + Type[ProductInputsServiceTransport], + Callable[..., ProductInputsServiceTransport], + ] = ( + ProductInputsServiceClient.get_transport_class(transport) + if isinstance(transport, str) or transport is None + else cast(Callable[..., ProductInputsServiceTransport], transport) + ) + # initialize with the provided callable or the passed in class + self._transport = transport_init( + credentials=credentials, + credentials_file=self._client_options.credentials_file, + host=self._api_endpoint, + scopes=self._client_options.scopes, + client_cert_source_for_mtls=self._client_cert_source, + quota_project_id=self._client_options.quota_project_id, + client_info=client_info, + always_use_jwt_access=True, + api_audience=self._client_options.api_audience, + ) + + if "async" not in str(self._transport): + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + std_logging.DEBUG + ): # pragma: NO COVER + _LOGGER.debug( + "Created client `google.shopping.merchant.products_v1.ProductInputsServiceClient`.", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductInputsService", + "universeDomain": getattr( + self._transport._credentials, "universe_domain", "" + ), + "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}", + "credentialsInfo": getattr( + self.transport._credentials, "get_cred_info", lambda: None + )(), + } + if hasattr(self._transport, "_credentials") + else { + "serviceName": "google.shopping.merchant.products.v1.ProductInputsService", + "credentialsType": None, + }, + ) + + def insert_product_input( + self, + request: Optional[Union[productinputs.InsertProductInputRequest, dict]] = None, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> productinputs.ProductInput: + r"""`Uploads a product input to your Merchant Center + account `__. + You must have a products `data + source `__ to be + able to insert a product. The unique identifier of the data + source is passed as a query parameter in the request URL. + + If a product input with the same contentLanguage, offerId, and + dataSource already exists, then the product input inserted by + this method replaces that entry. + + After inserting, updating, or deleting a product input, it may + take several minutes before the processed product can be + retrieved. + + .. 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.shopping import merchant_products_v1 + + def sample_insert_product_input(): + # Create a client + client = merchant_products_v1.ProductInputsServiceClient() + + # Initialize request argument(s) + product_input = merchant_products_v1.ProductInput() + product_input.offer_id = "offer_id_value" + product_input.content_language = "content_language_value" + product_input.feed_label = "feed_label_value" + + request = merchant_products_v1.InsertProductInputRequest( + parent="parent_value", + product_input=product_input, + data_source="data_source_value", + ) + + # Make the request + response = client.insert_product_input(request=request) + + # Handle the response + print(response) + + Args: + request (Union[google.shopping.merchant_products_v1.types.InsertProductInputRequest, dict]): + The request object. Request message for the + InsertProductInput method. + 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.shopping.merchant_products_v1.types.ProductInput: + This resource represents input data you submit for a product, not the + processed product that you see in Merchant Center, in + Shopping ads, or across Google surfaces. Product + inputs, rules and supplemental data source data are + combined to create the processed + [Product][google.shopping.merchant.products.v1.Product]. + For more information, see [Manage + products](/merchant/api/guides/products/overview). + + Required product input attributes to pass data + validation checks are primarily defined in the + [Products Data + Specification](\ https://support.google.com/merchants/answer/188494). + + The following attributes are required: + [feedLabel][google.shopping.merchant.products.v1.Product.feed_label], + [contentLanguage][google.shopping.merchant.products.v1.Product.content_language] + and + [offerId][google.shopping.merchant.products.v1.Product.offer_id]. + + After inserting, updating, or deleting a product + input, it may take several minutes before the + processed product can be retrieved. + + All fields in the product input and its sub-messages + match the English name of their corresponding + attribute in the [Products Data + Specification](\ https://support.google.com/merchants/answer/188494) + with [some + exceptions](\ https://support.google.com/merchants/answer/7052112). + The following reference documentation lists the field + names in the **camelCase** casing style while the + Products Data Specification lists the names in the + **snake_case** casing style. + + """ + # Create or coerce a protobuf request object. + # - 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, productinputs.InsertProductInputRequest): + request = productinputs.InsertProductInputRequest(request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.insert_product_input] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Validate the universe domain. + self._validate_universe_domain() + + # Send the request. + response = rpc( + request, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Done; return the response. + return response + + def update_product_input( + self, + request: Optional[Union[productinputs.UpdateProductInputRequest, dict]] = None, + *, + product_input: Optional[productinputs.ProductInput] = 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]]] = (), + ) -> productinputs.ProductInput: + r"""Updates the existing product input in your Merchant + Center account. + After inserting, updating, or deleting a product input, + it may take several minutes before the processed product + can be retrieved. + + .. 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.shopping import merchant_products_v1 + + def sample_update_product_input(): + # Create a client + client = merchant_products_v1.ProductInputsServiceClient() + + # Initialize request argument(s) + product_input = merchant_products_v1.ProductInput() + product_input.offer_id = "offer_id_value" + product_input.content_language = "content_language_value" + product_input.feed_label = "feed_label_value" + + request = merchant_products_v1.UpdateProductInputRequest( + product_input=product_input, + data_source="data_source_value", + ) + + # Make the request + response = client.update_product_input(request=request) + + # Handle the response + print(response) + + Args: + request (Union[google.shopping.merchant_products_v1.types.UpdateProductInputRequest, dict]): + The request object. Request message for the + UpdateProductInput method. The product + (primary input) must exist for the + update to succeed. If the update is for + a primary product input, the existing + primary product input must be from the + same data source. + product_input (google.shopping.merchant_products_v1.types.ProductInput): + Required. The product input resource + to update. Information you submit will + be applied to the processed product as + well. + + This corresponds to the ``product_input`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + update_mask (google.protobuf.field_mask_pb2.FieldMask): + Optional. The list of product attributes to be updated. + + If the update mask is omitted, then it is treated as + implied field mask equivalent to all fields that are + populated (have a non-empty value). + + Attributes specified in the update mask without a value + specified in the body will be deleted from the product. + + Update mask can only be specified for top level fields + in attributes and custom attributes. + + To specify the update mask for custom attributes you + need to add the ``custom_attribute.`` prefix. + + Providing special "*" value for full product replacement + is not supported. + + 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.shopping.merchant_products_v1.types.ProductInput: + This resource represents input data you submit for a product, not the + processed product that you see in Merchant Center, in + Shopping ads, or across Google surfaces. Product + inputs, rules and supplemental data source data are + combined to create the processed + [Product][google.shopping.merchant.products.v1.Product]. + For more information, see [Manage + products](/merchant/api/guides/products/overview). + + Required product input attributes to pass data + validation checks are primarily defined in the + [Products Data + Specification](\ https://support.google.com/merchants/answer/188494). + + The following attributes are required: + [feedLabel][google.shopping.merchant.products.v1.Product.feed_label], + [contentLanguage][google.shopping.merchant.products.v1.Product.content_language] + and + [offerId][google.shopping.merchant.products.v1.Product.offer_id]. + + After inserting, updating, or deleting a product + input, it may take several minutes before the + processed product can be retrieved. + + All fields in the product input and its sub-messages + match the English name of their corresponding + attribute in the [Products Data + Specification](\ https://support.google.com/merchants/answer/188494) + with [some + exceptions](\ https://support.google.com/merchants/answer/7052112). + The following reference documentation lists the field + names in the **camelCase** casing style while the + Products Data Specification lists the names in the + **snake_case** casing style. + + """ + # 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 = [product_input, 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, productinputs.UpdateProductInputRequest): + request = productinputs.UpdateProductInputRequest(request) + # If we have keyword arguments corresponding to fields on the + # request, apply these. + if product_input is not None: + request.product_input = product_input + 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_product_input] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata( + (("product_input.name", request.product_input.name),) + ), + ) + + # Validate the universe domain. + self._validate_universe_domain() + + # Send the request. + response = rpc( + request, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Done; return the response. + return response + + def delete_product_input( + self, + request: Optional[Union[productinputs.DeleteProductInputRequest, dict]] = None, + *, + name: Optional[str] = None, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> None: + r"""Deletes a product input from your Merchant Center + account. + After inserting, updating, or deleting a product input, + it may take several minutes before the processed product + can be retrieved. + + .. 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.shopping import merchant_products_v1 + + def sample_delete_product_input(): + # Create a client + client = merchant_products_v1.ProductInputsServiceClient() + + # Initialize request argument(s) + request = merchant_products_v1.DeleteProductInputRequest( + name="name_value", + data_source="data_source_value", + ) + + # Make the request + client.delete_product_input(request=request) + + Args: + request (Union[google.shopping.merchant_products_v1.types.DeleteProductInputRequest, dict]): + The request object. Request message for the + DeleteProductInput method. + name (str): + Required. The name of the product input resource to + delete. Format: + ``accounts/{account}/productInputs/{product}`` where the + last section ``product`` consists of: + ``content_language~feed_label~offer_id`` example for + product name is + ``accounts/123/productInputs/en~US~sku123``. + + This corresponds to the ``name`` 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`. + """ + # 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 = [name] + 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, productinputs.DeleteProductInputRequest): + request = productinputs.DeleteProductInputRequest(request) + # If we have keyword arguments corresponding to fields on the + # request, apply these. + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.delete_product_input] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Validate the universe domain. + self._validate_universe_domain() + + # Send the request. + rpc( + request, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + def __enter__(self) -> "ProductInputsServiceClient": + return self + + def __exit__(self, type, value, traceback): + """Releases underlying transport's resources. + + .. warning:: + ONLY use as a context manager if the transport is NOT shared + with other clients! Exiting the with block will CLOSE the transport + and may cause errors in other clients! + """ + self.transport.close() + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=package_version.__version__ +) + +if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"): # pragma: NO COVER + DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__ + +__all__ = ("ProductInputsServiceClient",) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/README.rst b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/README.rst new file mode 100644 index 000000000000..fa031e4be3c9 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/README.rst @@ -0,0 +1,9 @@ + +transport inheritance structure +_______________________________ + +`ProductInputsServiceTransport` is the ABC for all transports. +- public child `ProductInputsServiceGrpcTransport` for sync gRPC transport (defined in `grpc.py`). +- public child `ProductInputsServiceGrpcAsyncIOTransport` for async gRPC transport (defined in `grpc_asyncio.py`). +- private child `_BaseProductInputsServiceRestTransport` for base REST transport with inner classes `_BaseMETHOD` (defined in `rest_base.py`). +- public child `ProductInputsServiceRestTransport` for sync REST transport with inner classes `METHOD` derived from the parent's corresponding `_BaseMETHOD` classes (defined in `rest.py`). diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/__init__.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/__init__.py new file mode 100644 index 000000000000..7f7fc829ba01 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/__init__.py @@ -0,0 +1,38 @@ +# -*- 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 collections import OrderedDict +from typing import Dict, Type + +from .base import ProductInputsServiceTransport +from .grpc import ProductInputsServiceGrpcTransport +from .grpc_asyncio import ProductInputsServiceGrpcAsyncIOTransport +from .rest import ProductInputsServiceRestInterceptor, ProductInputsServiceRestTransport + +# Compile a registry of transports. +_transport_registry = ( + OrderedDict() +) # type: Dict[str, Type[ProductInputsServiceTransport]] +_transport_registry["grpc"] = ProductInputsServiceGrpcTransport +_transport_registry["grpc_asyncio"] = ProductInputsServiceGrpcAsyncIOTransport +_transport_registry["rest"] = ProductInputsServiceRestTransport + +__all__ = ( + "ProductInputsServiceTransport", + "ProductInputsServiceGrpcTransport", + "ProductInputsServiceGrpcAsyncIOTransport", + "ProductInputsServiceRestTransport", + "ProductInputsServiceRestInterceptor", +) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/base.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/base.py new file mode 100644 index 000000000000..d6f01c2399a1 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/base.py @@ -0,0 +1,194 @@ +# -*- 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. +# +import abc +from typing import Awaitable, Callable, Dict, Optional, Sequence, Union + +import google.api_core +from google.api_core import exceptions as core_exceptions +from google.api_core import gapic_v1 +from google.api_core import retry as retries +import google.auth # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.oauth2 import service_account # type: ignore +import google.protobuf +from google.protobuf import empty_pb2 # type: ignore + +from google.shopping.merchant_products_v1 import gapic_version as package_version +from google.shopping.merchant_products_v1.types import productinputs + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=package_version.__version__ +) + +if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"): # pragma: NO COVER + DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__ + + +class ProductInputsServiceTransport(abc.ABC): + """Abstract transport class for ProductInputsService.""" + + AUTH_SCOPES = ("https://www.googleapis.com/auth/content",) + + DEFAULT_HOST: str = "merchantapi.googleapis.com" + + def __init__( + self, + *, + host: str = DEFAULT_HOST, + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + api_audience: Optional[str] = None, + **kwargs, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to (default: 'merchantapi.googleapis.com'). + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. + scopes (Optional[Sequence[str]]): A list of scopes. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + """ + + scopes_kwargs = {"scopes": scopes, "default_scopes": self.AUTH_SCOPES} + + # Save the scopes. + self._scopes = scopes + if not hasattr(self, "_ignore_credentials"): + self._ignore_credentials: bool = False + + # If no credentials are provided, then determine the appropriate + # defaults. + if credentials and credentials_file: + raise core_exceptions.DuplicateCredentialArgs( + "'credentials_file' and 'credentials' are mutually exclusive" + ) + + if credentials_file is not None: + credentials, _ = google.auth.load_credentials_from_file( + credentials_file, **scopes_kwargs, quota_project_id=quota_project_id + ) + elif credentials is None and not self._ignore_credentials: + credentials, _ = google.auth.default( + **scopes_kwargs, quota_project_id=quota_project_id + ) + # Don't apply audience if the credentials file passed from user. + if hasattr(credentials, "with_gdch_audience"): + credentials = credentials.with_gdch_audience( + api_audience if api_audience else host + ) + + # If the credentials are service account credentials, then always try to use self signed JWT. + if ( + always_use_jwt_access + and isinstance(credentials, service_account.Credentials) + and hasattr(service_account.Credentials, "with_always_use_jwt_access") + ): + credentials = credentials.with_always_use_jwt_access(True) + + # Save the credentials. + self._credentials = credentials + + # Save the hostname. Default to port 443 (HTTPS) if none is specified. + if ":" not in host: + host += ":443" + self._host = host + + @property + def host(self): + return self._host + + def _prep_wrapped_messages(self, client_info): + # Precompute the wrapped methods. + self._wrapped_methods = { + self.insert_product_input: gapic_v1.method.wrap_method( + self.insert_product_input, + default_timeout=None, + client_info=client_info, + ), + self.update_product_input: gapic_v1.method.wrap_method( + self.update_product_input, + default_timeout=None, + client_info=client_info, + ), + self.delete_product_input: gapic_v1.method.wrap_method( + self.delete_product_input, + default_timeout=None, + client_info=client_info, + ), + } + + def close(self): + """Closes resources associated with the transport. + + .. warning:: + Only call this method if the transport is NOT shared + with other clients - this may cause errors in other clients! + """ + raise NotImplementedError() + + @property + def insert_product_input( + self, + ) -> Callable[ + [productinputs.InsertProductInputRequest], + Union[productinputs.ProductInput, Awaitable[productinputs.ProductInput]], + ]: + raise NotImplementedError() + + @property + def update_product_input( + self, + ) -> Callable[ + [productinputs.UpdateProductInputRequest], + Union[productinputs.ProductInput, Awaitable[productinputs.ProductInput]], + ]: + raise NotImplementedError() + + @property + def delete_product_input( + self, + ) -> Callable[ + [productinputs.DeleteProductInputRequest], + Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]], + ]: + raise NotImplementedError() + + @property + def kind(self) -> str: + raise NotImplementedError() + + +__all__ = ("ProductInputsServiceTransport",) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/grpc.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/grpc.py new file mode 100644 index 000000000000..84d1b27aa1db --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/grpc.py @@ -0,0 +1,434 @@ +# -*- 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. +# +import json +import logging as std_logging +import pickle +from typing import Callable, Dict, Optional, Sequence, Tuple, Union +import warnings + +from google.api_core import gapic_v1, grpc_helpers +import google.auth # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.protobuf import empty_pb2 # type: ignore +from google.protobuf.json_format import MessageToJson +import google.protobuf.message +import grpc # type: ignore +import proto # type: ignore + +from google.shopping.merchant_products_v1.types import productinputs + +from .base import DEFAULT_CLIENT_INFO, ProductInputsServiceTransport + +try: + from google.api_core import client_logging # type: ignore + + CLIENT_LOGGING_SUPPORTED = True # pragma: NO COVER +except ImportError: # pragma: NO COVER + CLIENT_LOGGING_SUPPORTED = False + +_LOGGER = std_logging.getLogger(__name__) + + +class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor): # pragma: NO COVER + def intercept_unary_unary(self, continuation, client_call_details, request): + logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + std_logging.DEBUG + ) + if logging_enabled: # pragma: NO COVER + request_metadata = client_call_details.metadata + if isinstance(request, proto.Message): + request_payload = type(request).to_json(request) + elif isinstance(request, google.protobuf.message.Message): + request_payload = MessageToJson(request) + else: + request_payload = f"{type(request).__name__}: {pickle.dumps(request)}" + + request_metadata = { + key: value.decode("utf-8") if isinstance(value, bytes) else value + for key, value in request_metadata + } + grpc_request = { + "payload": request_payload, + "requestMethod": "grpc", + "metadata": dict(request_metadata), + } + _LOGGER.debug( + f"Sending request for {client_call_details.method}", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductInputsService", + "rpcName": str(client_call_details.method), + "request": grpc_request, + "metadata": grpc_request["metadata"], + }, + ) + response = continuation(client_call_details, request) + if logging_enabled: # pragma: NO COVER + response_metadata = response.trailing_metadata() + # Convert gRPC metadata `` to list of tuples + metadata = ( + dict([(k, str(v)) for k, v in response_metadata]) + if response_metadata + else None + ) + result = response.result() + if isinstance(result, proto.Message): + response_payload = type(result).to_json(result) + elif isinstance(result, google.protobuf.message.Message): + response_payload = MessageToJson(result) + else: + response_payload = f"{type(result).__name__}: {pickle.dumps(result)}" + grpc_response = { + "payload": response_payload, + "metadata": metadata, + "status": "OK", + } + _LOGGER.debug( + f"Received response for {client_call_details.method}.", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductInputsService", + "rpcName": client_call_details.method, + "response": grpc_response, + "metadata": grpc_response["metadata"], + }, + ) + return response + + +class ProductInputsServiceGrpcTransport(ProductInputsServiceTransport): + """gRPC backend transport for ProductInputsService. + + Service to use ProductInput resource. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends protocol buffers over the wire using gRPC (which is built on + top of HTTP/2); the ``grpcio`` package must be installed. + """ + + _stubs: Dict[str, Callable] + + def __init__( + self, + *, + host: str = "merchantapi.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None, + api_mtls_endpoint: Optional[str] = None, + client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to (default: 'merchantapi.googleapis.com'). + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + This argument is ignored if a ``channel`` instance is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if a ``channel`` instance is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if a ``channel`` instance is provided. + channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]): + A ``Channel`` instance through which to make calls, or a Callable + that constructs and returns one. If set to None, ``self.create_channel`` + is used to create the channel. If a Callable is given, it will be called + with the same arguments as used in ``self.create_channel``. + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create + a mutual TLS channel with client SSL credentials from + ``client_cert_source`` or application default SSL credentials. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for the grpc channel. It is ignored if a ``channel`` instance is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure a mutual TLS channel. It is + ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport + creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + self._grpc_channel = None + self._ssl_channel_credentials = ssl_channel_credentials + self._stubs: Dict[str, Callable] = {} + + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + + if isinstance(channel, grpc.Channel): + # Ignore credentials if a channel was passed. + credentials = None + self._ignore_credentials = True + # If a channel was explicitly provided, set it. + self._grpc_channel = channel + self._ssl_channel_credentials = None + + else: + if api_mtls_endpoint: + host = api_mtls_endpoint + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + self._ssl_channel_credentials = SslCredentials().ssl_credentials + + else: + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + + # The base transport sets the host, credentials and scopes + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + + if not self._grpc_channel: + # initialize with the provided callable or the default channel + channel_init = channel or type(self).create_channel + self._grpc_channel = channel_init( + self._host, + # use the credentials which are saved + credentials=self._credentials, + # Set ``credentials_file`` to ``None`` here as + # the credentials that we saved earlier should be used. + credentials_file=None, + scopes=self._scopes, + ssl_credentials=self._ssl_channel_credentials, + quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + self._interceptor = _LoggingClientInterceptor() + self._logged_channel = grpc.intercept_channel( + self._grpc_channel, self._interceptor + ) + + # Wrap messages. This must be done after self._logged_channel exists + self._prep_wrapped_messages(client_info) + + @classmethod + def create_channel( + cls, + host: str = "merchantapi.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, + **kwargs, + ) -> grpc.Channel: + """Create and return a gRPC channel object. + Args: + host (Optional[str]): The host for the channel to use. + credentials (Optional[~.Credentials]): The + authorization credentials to attach to requests. These + credentials identify this application to the service. If + none are specified, the client will attempt to ascertain + the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + kwargs (Optional[dict]): Keyword arguments, which are passed to the + channel creation. + Returns: + grpc.Channel: A gRPC channel object. + + Raises: + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + + return grpc_helpers.create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + quota_project_id=quota_project_id, + default_scopes=cls.AUTH_SCOPES, + scopes=scopes, + default_host=cls.DEFAULT_HOST, + **kwargs, + ) + + @property + def grpc_channel(self) -> grpc.Channel: + """Return the channel designed to connect to this service.""" + return self._grpc_channel + + @property + def insert_product_input( + self, + ) -> Callable[ + [productinputs.InsertProductInputRequest], productinputs.ProductInput + ]: + r"""Return a callable for the insert product input method over gRPC. + + `Uploads a product input to your Merchant Center + account `__. + You must have a products `data + source `__ to be + able to insert a product. The unique identifier of the data + source is passed as a query parameter in the request URL. + + If a product input with the same contentLanguage, offerId, and + dataSource already exists, then the product input inserted by + this method replaces that entry. + + After inserting, updating, or deleting a product input, it may + take several minutes before the processed product can be + retrieved. + + Returns: + Callable[[~.InsertProductInputRequest], + ~.ProductInput]: + 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 "insert_product_input" not in self._stubs: + self._stubs["insert_product_input"] = self._logged_channel.unary_unary( + "/google.shopping.merchant.products.v1.ProductInputsService/InsertProductInput", + request_serializer=productinputs.InsertProductInputRequest.serialize, + response_deserializer=productinputs.ProductInput.deserialize, + ) + return self._stubs["insert_product_input"] + + @property + def update_product_input( + self, + ) -> Callable[ + [productinputs.UpdateProductInputRequest], productinputs.ProductInput + ]: + r"""Return a callable for the update product input method over gRPC. + + Updates the existing product input in your Merchant + Center account. + After inserting, updating, or deleting a product input, + it may take several minutes before the processed product + can be retrieved. + + Returns: + Callable[[~.UpdateProductInputRequest], + ~.ProductInput]: + 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_product_input" not in self._stubs: + self._stubs["update_product_input"] = self._logged_channel.unary_unary( + "/google.shopping.merchant.products.v1.ProductInputsService/UpdateProductInput", + request_serializer=productinputs.UpdateProductInputRequest.serialize, + response_deserializer=productinputs.ProductInput.deserialize, + ) + return self._stubs["update_product_input"] + + @property + def delete_product_input( + self, + ) -> Callable[[productinputs.DeleteProductInputRequest], empty_pb2.Empty]: + r"""Return a callable for the delete product input method over gRPC. + + Deletes a product input from your Merchant Center + account. + After inserting, updating, or deleting a product input, + it may take several minutes before the processed product + can be retrieved. + + Returns: + Callable[[~.DeleteProductInputRequest], + ~.Empty]: + 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 "delete_product_input" not in self._stubs: + self._stubs["delete_product_input"] = self._logged_channel.unary_unary( + "/google.shopping.merchant.products.v1.ProductInputsService/DeleteProductInput", + request_serializer=productinputs.DeleteProductInputRequest.serialize, + response_deserializer=empty_pb2.Empty.FromString, + ) + return self._stubs["delete_product_input"] + + def close(self): + self._logged_channel.close() + + @property + def kind(self) -> str: + return "grpc" + + +__all__ = ("ProductInputsServiceGrpcTransport",) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/grpc_asyncio.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/grpc_asyncio.py new file mode 100644 index 000000000000..9f8b0b184575 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/grpc_asyncio.py @@ -0,0 +1,469 @@ +# -*- 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. +# +import inspect +import json +import logging as std_logging +import pickle +from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union +import warnings + +from google.api_core import exceptions as core_exceptions +from google.api_core import gapic_v1, grpc_helpers_async +from google.api_core import retry_async as retries +from google.auth import credentials as ga_credentials # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.protobuf import empty_pb2 # type: ignore +from google.protobuf.json_format import MessageToJson +import google.protobuf.message +import grpc # type: ignore +from grpc.experimental import aio # type: ignore +import proto # type: ignore + +from google.shopping.merchant_products_v1.types import productinputs + +from .base import DEFAULT_CLIENT_INFO, ProductInputsServiceTransport +from .grpc import ProductInputsServiceGrpcTransport + +try: + from google.api_core import client_logging # type: ignore + + CLIENT_LOGGING_SUPPORTED = True # pragma: NO COVER +except ImportError: # pragma: NO COVER + CLIENT_LOGGING_SUPPORTED = False + +_LOGGER = std_logging.getLogger(__name__) + + +class _LoggingClientAIOInterceptor( + grpc.aio.UnaryUnaryClientInterceptor +): # pragma: NO COVER + async def intercept_unary_unary(self, continuation, client_call_details, request): + logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + std_logging.DEBUG + ) + if logging_enabled: # pragma: NO COVER + request_metadata = client_call_details.metadata + if isinstance(request, proto.Message): + request_payload = type(request).to_json(request) + elif isinstance(request, google.protobuf.message.Message): + request_payload = MessageToJson(request) + else: + request_payload = f"{type(request).__name__}: {pickle.dumps(request)}" + + request_metadata = { + key: value.decode("utf-8") if isinstance(value, bytes) else value + for key, value in request_metadata + } + grpc_request = { + "payload": request_payload, + "requestMethod": "grpc", + "metadata": dict(request_metadata), + } + _LOGGER.debug( + f"Sending request for {client_call_details.method}", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductInputsService", + "rpcName": str(client_call_details.method), + "request": grpc_request, + "metadata": grpc_request["metadata"], + }, + ) + response = await continuation(client_call_details, request) + if logging_enabled: # pragma: NO COVER + response_metadata = await response.trailing_metadata() + # Convert gRPC metadata `` to list of tuples + metadata = ( + dict([(k, str(v)) for k, v in response_metadata]) + if response_metadata + else None + ) + result = await response + if isinstance(result, proto.Message): + response_payload = type(result).to_json(result) + elif isinstance(result, google.protobuf.message.Message): + response_payload = MessageToJson(result) + else: + response_payload = f"{type(result).__name__}: {pickle.dumps(result)}" + grpc_response = { + "payload": response_payload, + "metadata": metadata, + "status": "OK", + } + _LOGGER.debug( + f"Received response to rpc {client_call_details.method}.", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductInputsService", + "rpcName": str(client_call_details.method), + "response": grpc_response, + "metadata": grpc_response["metadata"], + }, + ) + return response + + +class ProductInputsServiceGrpcAsyncIOTransport(ProductInputsServiceTransport): + """gRPC AsyncIO backend transport for ProductInputsService. + + Service to use ProductInput resource. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends protocol buffers over the wire using gRPC (which is built on + top of HTTP/2); the ``grpcio`` package must be installed. + """ + + _grpc_channel: aio.Channel + _stubs: Dict[str, Callable] = {} + + @classmethod + def create_channel( + cls, + host: str = "merchantapi.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, + **kwargs, + ) -> aio.Channel: + """Create and return a gRPC AsyncIO channel object. + Args: + host (Optional[str]): The host for the channel to use. + credentials (Optional[~.Credentials]): The + authorization credentials to attach to requests. These + credentials identify this application to the service. If + none are specified, the client will attempt to ascertain + the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + kwargs (Optional[dict]): Keyword arguments, which are passed to the + channel creation. + Returns: + aio.Channel: A gRPC AsyncIO channel object. + """ + + return grpc_helpers_async.create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + quota_project_id=quota_project_id, + default_scopes=cls.AUTH_SCOPES, + scopes=scopes, + default_host=cls.DEFAULT_HOST, + **kwargs, + ) + + def __init__( + self, + *, + host: str = "merchantapi.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None, + api_mtls_endpoint: Optional[str] = None, + client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to (default: 'merchantapi.googleapis.com'). + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + This argument is ignored if a ``channel`` instance is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if a ``channel`` instance is provided. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]): + A ``Channel`` instance through which to make calls, or a Callable + that constructs and returns one. If set to None, ``self.create_channel`` + is used to create the channel. If a Callable is given, it will be called + with the same arguments as used in ``self.create_channel``. + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create + a mutual TLS channel with client SSL credentials from + ``client_cert_source`` or application default SSL credentials. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for the grpc channel. It is ignored if a ``channel`` instance is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure a mutual TLS channel. It is + ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + self._grpc_channel = None + self._ssl_channel_credentials = ssl_channel_credentials + self._stubs: Dict[str, Callable] = {} + + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + + if isinstance(channel, aio.Channel): + # Ignore credentials if a channel was passed. + credentials = None + self._ignore_credentials = True + # If a channel was explicitly provided, set it. + self._grpc_channel = channel + self._ssl_channel_credentials = None + else: + if api_mtls_endpoint: + host = api_mtls_endpoint + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + self._ssl_channel_credentials = SslCredentials().ssl_credentials + + else: + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + + # The base transport sets the host, credentials and scopes + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + + if not self._grpc_channel: + # initialize with the provided callable or the default channel + channel_init = channel or type(self).create_channel + self._grpc_channel = channel_init( + self._host, + # use the credentials which are saved + credentials=self._credentials, + # Set ``credentials_file`` to ``None`` here as + # the credentials that we saved earlier should be used. + credentials_file=None, + scopes=self._scopes, + ssl_credentials=self._ssl_channel_credentials, + quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + self._interceptor = _LoggingClientAIOInterceptor() + self._grpc_channel._unary_unary_interceptors.append(self._interceptor) + self._logged_channel = self._grpc_channel + self._wrap_with_kind = ( + "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters + ) + # Wrap messages. This must be done after self._logged_channel exists + self._prep_wrapped_messages(client_info) + + @property + def grpc_channel(self) -> aio.Channel: + """Create the channel designed to connect to this service. + + This property caches on the instance; repeated calls return + the same channel. + """ + # Return the channel from cache. + return self._grpc_channel + + @property + def insert_product_input( + self, + ) -> Callable[ + [productinputs.InsertProductInputRequest], Awaitable[productinputs.ProductInput] + ]: + r"""Return a callable for the insert product input method over gRPC. + + `Uploads a product input to your Merchant Center + account `__. + You must have a products `data + source `__ to be + able to insert a product. The unique identifier of the data + source is passed as a query parameter in the request URL. + + If a product input with the same contentLanguage, offerId, and + dataSource already exists, then the product input inserted by + this method replaces that entry. + + After inserting, updating, or deleting a product input, it may + take several minutes before the processed product can be + retrieved. + + Returns: + Callable[[~.InsertProductInputRequest], + Awaitable[~.ProductInput]]: + 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 "insert_product_input" not in self._stubs: + self._stubs["insert_product_input"] = self._logged_channel.unary_unary( + "/google.shopping.merchant.products.v1.ProductInputsService/InsertProductInput", + request_serializer=productinputs.InsertProductInputRequest.serialize, + response_deserializer=productinputs.ProductInput.deserialize, + ) + return self._stubs["insert_product_input"] + + @property + def update_product_input( + self, + ) -> Callable[ + [productinputs.UpdateProductInputRequest], Awaitable[productinputs.ProductInput] + ]: + r"""Return a callable for the update product input method over gRPC. + + Updates the existing product input in your Merchant + Center account. + After inserting, updating, or deleting a product input, + it may take several minutes before the processed product + can be retrieved. + + Returns: + Callable[[~.UpdateProductInputRequest], + Awaitable[~.ProductInput]]: + 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_product_input" not in self._stubs: + self._stubs["update_product_input"] = self._logged_channel.unary_unary( + "/google.shopping.merchant.products.v1.ProductInputsService/UpdateProductInput", + request_serializer=productinputs.UpdateProductInputRequest.serialize, + response_deserializer=productinputs.ProductInput.deserialize, + ) + return self._stubs["update_product_input"] + + @property + def delete_product_input( + self, + ) -> Callable[ + [productinputs.DeleteProductInputRequest], Awaitable[empty_pb2.Empty] + ]: + r"""Return a callable for the delete product input method over gRPC. + + Deletes a product input from your Merchant Center + account. + After inserting, updating, or deleting a product input, + it may take several minutes before the processed product + can be retrieved. + + Returns: + Callable[[~.DeleteProductInputRequest], + Awaitable[~.Empty]]: + 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 "delete_product_input" not in self._stubs: + self._stubs["delete_product_input"] = self._logged_channel.unary_unary( + "/google.shopping.merchant.products.v1.ProductInputsService/DeleteProductInput", + request_serializer=productinputs.DeleteProductInputRequest.serialize, + response_deserializer=empty_pb2.Empty.FromString, + ) + return self._stubs["delete_product_input"] + + def _prep_wrapped_messages(self, client_info): + """Precompute the wrapped methods, overriding the base class method to use async wrappers.""" + self._wrapped_methods = { + self.insert_product_input: self._wrap_method( + self.insert_product_input, + default_timeout=None, + client_info=client_info, + ), + self.update_product_input: self._wrap_method( + self.update_product_input, + default_timeout=None, + client_info=client_info, + ), + self.delete_product_input: self._wrap_method( + self.delete_product_input, + default_timeout=None, + client_info=client_info, + ), + } + + def _wrap_method(self, func, *args, **kwargs): + if self._wrap_with_kind: # pragma: NO COVER + kwargs["kind"] = self.kind + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) + + def close(self): + return self._logged_channel.close() + + @property + def kind(self) -> str: + return "grpc_asyncio" + + +__all__ = ("ProductInputsServiceGrpcAsyncIOTransport",) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/rest.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/rest.py new file mode 100644 index 000000000000..badfb5a6ac92 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/rest.py @@ -0,0 +1,832 @@ +# -*- 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. +# +import dataclasses +import json # type: ignore +import logging +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +from google.api_core import exceptions as core_exceptions +from google.api_core import gapic_v1, rest_helpers, rest_streaming +from google.api_core import retry as retries +from google.auth import credentials as ga_credentials # type: ignore +from google.auth.transport.requests import AuthorizedSession # type: ignore +import google.protobuf +from google.protobuf import empty_pb2 # type: ignore +from google.protobuf import json_format +from requests import __version__ as requests_version + +from google.shopping.merchant_products_v1.types import productinputs + +from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO +from .rest_base import _BaseProductInputsServiceRestTransport + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object, None] # type: ignore + +try: + from google.api_core import client_logging # type: ignore + + CLIENT_LOGGING_SUPPORTED = True # pragma: NO COVER +except ImportError: # pragma: NO COVER + CLIENT_LOGGING_SUPPORTED = False + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=f"requests@{requests_version}", +) + +if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"): # pragma: NO COVER + DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__ + + +class ProductInputsServiceRestInterceptor: + """Interceptor for ProductInputsService. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the ProductInputsServiceRestTransport. + + .. code-block:: python + class MyCustomProductInputsServiceInterceptor(ProductInputsServiceRestInterceptor): + def pre_delete_product_input(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_insert_product_input(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_insert_product_input(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_product_input(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_product_input(self, response): + logging.log(f"Received response: {response}") + return response + + transport = ProductInputsServiceRestTransport(interceptor=MyCustomProductInputsServiceInterceptor()) + client = ProductInputsServiceClient(transport=transport) + + + """ + + def pre_delete_product_input( + self, + request: productinputs.DeleteProductInputRequest, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[ + productinputs.DeleteProductInputRequest, Sequence[Tuple[str, Union[str, bytes]]] + ]: + """Pre-rpc interceptor for delete_product_input + + Override in a subclass to manipulate the request or metadata + before they are sent to the ProductInputsService server. + """ + return request, metadata + + def pre_insert_product_input( + self, + request: productinputs.InsertProductInputRequest, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[ + productinputs.InsertProductInputRequest, Sequence[Tuple[str, Union[str, bytes]]] + ]: + """Pre-rpc interceptor for insert_product_input + + Override in a subclass to manipulate the request or metadata + before they are sent to the ProductInputsService server. + """ + return request, metadata + + def post_insert_product_input( + self, response: productinputs.ProductInput + ) -> productinputs.ProductInput: + """Post-rpc interceptor for insert_product_input + + DEPRECATED. Please use the `post_insert_product_input_with_metadata` + interceptor instead. + + Override in a subclass to read or manipulate the response + after it is returned by the ProductInputsService server but before + it is returned to user code. This `post_insert_product_input` interceptor runs + before the `post_insert_product_input_with_metadata` interceptor. + """ + return response + + def post_insert_product_input_with_metadata( + self, + response: productinputs.ProductInput, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[productinputs.ProductInput, Sequence[Tuple[str, Union[str, bytes]]]]: + """Post-rpc interceptor for insert_product_input + + Override in a subclass to read or manipulate the response or metadata after it + is returned by the ProductInputsService server but before it is returned to user code. + + We recommend only using this `post_insert_product_input_with_metadata` + interceptor in new development instead of the `post_insert_product_input` interceptor. + When both interceptors are used, this `post_insert_product_input_with_metadata` interceptor runs after the + `post_insert_product_input` interceptor. The (possibly modified) response returned by + `post_insert_product_input` will be passed to + `post_insert_product_input_with_metadata`. + """ + return response, metadata + + def pre_update_product_input( + self, + request: productinputs.UpdateProductInputRequest, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[ + productinputs.UpdateProductInputRequest, Sequence[Tuple[str, Union[str, bytes]]] + ]: + """Pre-rpc interceptor for update_product_input + + Override in a subclass to manipulate the request or metadata + before they are sent to the ProductInputsService server. + """ + return request, metadata + + def post_update_product_input( + self, response: productinputs.ProductInput + ) -> productinputs.ProductInput: + """Post-rpc interceptor for update_product_input + + DEPRECATED. Please use the `post_update_product_input_with_metadata` + interceptor instead. + + Override in a subclass to read or manipulate the response + after it is returned by the ProductInputsService server but before + it is returned to user code. This `post_update_product_input` interceptor runs + before the `post_update_product_input_with_metadata` interceptor. + """ + return response + + def post_update_product_input_with_metadata( + self, + response: productinputs.ProductInput, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[productinputs.ProductInput, Sequence[Tuple[str, Union[str, bytes]]]]: + """Post-rpc interceptor for update_product_input + + Override in a subclass to read or manipulate the response or metadata after it + is returned by the ProductInputsService server but before it is returned to user code. + + We recommend only using this `post_update_product_input_with_metadata` + interceptor in new development instead of the `post_update_product_input` interceptor. + When both interceptors are used, this `post_update_product_input_with_metadata` interceptor runs after the + `post_update_product_input` interceptor. The (possibly modified) response returned by + `post_update_product_input` will be passed to + `post_update_product_input_with_metadata`. + """ + return response, metadata + + +@dataclasses.dataclass +class ProductInputsServiceRestStub: + _session: AuthorizedSession + _host: str + _interceptor: ProductInputsServiceRestInterceptor + + +class ProductInputsServiceRestTransport(_BaseProductInputsServiceRestTransport): + """REST backend synchronous transport for ProductInputsService. + + Service to use ProductInput resource. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + """ + + def __init__( + self, + *, + host: str = "merchantapi.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[ProductInputsServiceRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to (default: 'merchantapi.googleapis.com'). + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` is provided. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + url_scheme=url_scheme, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or ProductInputsServiceRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _DeleteProductInput( + _BaseProductInputsServiceRestTransport._BaseDeleteProductInput, + ProductInputsServiceRestStub, + ): + def __hash__(self): + return hash("ProductInputsServiceRestTransport.DeleteProductInput") + + @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), + ) + return response + + def __call__( + self, + request: productinputs.DeleteProductInputRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ): + r"""Call the delete product input method over HTTP. + + Args: + request (~.productinputs.DeleteProductInputRequest): + The request object. Request message for the + DeleteProductInput method. + 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`. + """ + + http_options = ( + _BaseProductInputsServiceRestTransport._BaseDeleteProductInput._get_http_options() + ) + + request, metadata = self._interceptor.pre_delete_product_input( + request, metadata + ) + transcoded_request = _BaseProductInputsServiceRestTransport._BaseDeleteProductInput._get_transcoded_request( + http_options, request + ) + + # Jsonify the query params + query_params = _BaseProductInputsServiceRestTransport._BaseDeleteProductInput._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 = json_format.MessageToJson(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.shopping.merchant.products_v1.ProductInputsServiceClient.DeleteProductInput", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductInputsService", + "rpcName": "DeleteProductInput", + "httpRequest": http_request, + "metadata": http_request["headers"], + }, + ) + + # Send the request + response = ( + ProductInputsServiceRestTransport._DeleteProductInput._get_response( + self._host, + metadata, + query_params, + self._session, + timeout, + transcoded_request, + ) + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _InsertProductInput( + _BaseProductInputsServiceRestTransport._BaseInsertProductInput, + ProductInputsServiceRestStub, + ): + def __hash__(self): + return hash("ProductInputsServiceRestTransport.InsertProductInput") + + @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: productinputs.InsertProductInputRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> productinputs.ProductInput: + r"""Call the insert product input method over HTTP. + + Args: + request (~.productinputs.InsertProductInputRequest): + The request object. Request message for the + InsertProductInput method. + 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: + ~.productinputs.ProductInput: + This resource represents input data you submit for a + product, not the processed product that you see in + Merchant Center, in Shopping ads, or across Google + surfaces. Product inputs, rules and supplemental data + source data are combined to create the processed + [Product][google.shopping.merchant.products.v1.Product]. + For more information, see `Manage + products `__. + + Required product input attributes to pass data + validation checks are primarily defined in the `Products + Data + Specification `__. + + The following attributes are required: + [feedLabel][google.shopping.merchant.products.v1.Product.feed_label], + [contentLanguage][google.shopping.merchant.products.v1.Product.content_language] + and + [offerId][google.shopping.merchant.products.v1.Product.offer_id]. + + After inserting, updating, or deleting a product input, + it may take several minutes before the processed product + can be retrieved. + + All fields in the product input and its sub-messages + match the English name of their corresponding attribute + in the `Products Data + Specification `__ + with `some + exceptions `__. + The following reference documentation lists the field + names in the **camelCase** casing style while the + Products Data Specification lists the names in the + **snake_case** casing style. + + """ + + http_options = ( + _BaseProductInputsServiceRestTransport._BaseInsertProductInput._get_http_options() + ) + + request, metadata = self._interceptor.pre_insert_product_input( + request, metadata + ) + transcoded_request = _BaseProductInputsServiceRestTransport._BaseInsertProductInput._get_transcoded_request( + http_options, request + ) + + body = _BaseProductInputsServiceRestTransport._BaseInsertProductInput._get_request_body_json( + transcoded_request + ) + + # Jsonify the query params + query_params = _BaseProductInputsServiceRestTransport._BaseInsertProductInput._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.shopping.merchant.products_v1.ProductInputsServiceClient.InsertProductInput", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductInputsService", + "rpcName": "InsertProductInput", + "httpRequest": http_request, + "metadata": http_request["headers"], + }, + ) + + # Send the request + response = ( + ProductInputsServiceRestTransport._InsertProductInput._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 = productinputs.ProductInput() + pb_resp = productinputs.ProductInput.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + + resp = self._interceptor.post_insert_product_input(resp) + response_metadata = [(k, str(v)) for k, v in response.headers.items()] + resp, _ = self._interceptor.post_insert_product_input_with_metadata( + resp, response_metadata + ) + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + try: + response_payload = productinputs.ProductInput.to_json(response) + except: + response_payload = None + http_response = { + "payload": response_payload, + "headers": dict(response.headers), + "status": response.status_code, + } + _LOGGER.debug( + "Received response for google.shopping.merchant.products_v1.ProductInputsServiceClient.insert_product_input", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductInputsService", + "rpcName": "InsertProductInput", + "metadata": http_response["headers"], + "httpResponse": http_response, + }, + ) + return resp + + class _UpdateProductInput( + _BaseProductInputsServiceRestTransport._BaseUpdateProductInput, + ProductInputsServiceRestStub, + ): + def __hash__(self): + return hash("ProductInputsServiceRestTransport.UpdateProductInput") + + @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: productinputs.UpdateProductInputRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> productinputs.ProductInput: + r"""Call the update product input method over HTTP. + + Args: + request (~.productinputs.UpdateProductInputRequest): + The request object. Request message for the + UpdateProductInput method. The product + (primary input) must exist for the + update to succeed. If the update is for + a primary product input, the existing + primary product input must be from the + same data source. + 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: + ~.productinputs.ProductInput: + This resource represents input data you submit for a + product, not the processed product that you see in + Merchant Center, in Shopping ads, or across Google + surfaces. Product inputs, rules and supplemental data + source data are combined to create the processed + [Product][google.shopping.merchant.products.v1.Product]. + For more information, see `Manage + products `__. + + Required product input attributes to pass data + validation checks are primarily defined in the `Products + Data + Specification `__. + + The following attributes are required: + [feedLabel][google.shopping.merchant.products.v1.Product.feed_label], + [contentLanguage][google.shopping.merchant.products.v1.Product.content_language] + and + [offerId][google.shopping.merchant.products.v1.Product.offer_id]. + + After inserting, updating, or deleting a product input, + it may take several minutes before the processed product + can be retrieved. + + All fields in the product input and its sub-messages + match the English name of their corresponding attribute + in the `Products Data + Specification `__ + with `some + exceptions `__. + The following reference documentation lists the field + names in the **camelCase** casing style while the + Products Data Specification lists the names in the + **snake_case** casing style. + + """ + + http_options = ( + _BaseProductInputsServiceRestTransport._BaseUpdateProductInput._get_http_options() + ) + + request, metadata = self._interceptor.pre_update_product_input( + request, metadata + ) + transcoded_request = _BaseProductInputsServiceRestTransport._BaseUpdateProductInput._get_transcoded_request( + http_options, request + ) + + body = _BaseProductInputsServiceRestTransport._BaseUpdateProductInput._get_request_body_json( + transcoded_request + ) + + # Jsonify the query params + query_params = _BaseProductInputsServiceRestTransport._BaseUpdateProductInput._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.shopping.merchant.products_v1.ProductInputsServiceClient.UpdateProductInput", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductInputsService", + "rpcName": "UpdateProductInput", + "httpRequest": http_request, + "metadata": http_request["headers"], + }, + ) + + # Send the request + response = ( + ProductInputsServiceRestTransport._UpdateProductInput._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 = productinputs.ProductInput() + pb_resp = productinputs.ProductInput.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + + resp = self._interceptor.post_update_product_input(resp) + response_metadata = [(k, str(v)) for k, v in response.headers.items()] + resp, _ = self._interceptor.post_update_product_input_with_metadata( + resp, response_metadata + ) + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + try: + response_payload = productinputs.ProductInput.to_json(response) + except: + response_payload = None + http_response = { + "payload": response_payload, + "headers": dict(response.headers), + "status": response.status_code, + } + _LOGGER.debug( + "Received response for google.shopping.merchant.products_v1.ProductInputsServiceClient.update_product_input", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductInputsService", + "rpcName": "UpdateProductInput", + "metadata": http_response["headers"], + "httpResponse": http_response, + }, + ) + return resp + + @property + def delete_product_input( + self, + ) -> Callable[[productinputs.DeleteProductInputRequest], empty_pb2.Empty]: + # 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._DeleteProductInput(self._session, self._host, self._interceptor) # type: ignore + + @property + def insert_product_input( + self, + ) -> Callable[ + [productinputs.InsertProductInputRequest], productinputs.ProductInput + ]: + # 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._InsertProductInput(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_product_input( + self, + ) -> Callable[ + [productinputs.UpdateProductInputRequest], productinputs.ProductInput + ]: + # 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._UpdateProductInput(self._session, self._host, self._interceptor) # type: ignore + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("ProductInputsServiceRestTransport",) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/rest_base.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/rest_base.py new file mode 100644 index 000000000000..d798b3166ebf --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/product_inputs_service/transports/rest_base.py @@ -0,0 +1,259 @@ +# -*- 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. +# +import json # type: ignore +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union + +from google.api_core import gapic_v1, path_template +from google.protobuf import empty_pb2 # type: ignore +from google.protobuf import json_format + +from google.shopping.merchant_products_v1.types import productinputs + +from .base import DEFAULT_CLIENT_INFO, ProductInputsServiceTransport + + +class _BaseProductInputsServiceRestTransport(ProductInputsServiceTransport): + """Base REST backend transport for ProductInputsService. + + Note: This class is not meant to be used directly. Use its sync and + async sub-classes instead. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + """ + + def __init__( + self, + *, + host: str = "merchantapi.googleapis.com", + credentials: Optional[Any] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + Args: + host (Optional[str]): + The hostname to connect to (default: 'merchantapi.googleapis.com'). + credentials (Optional[Any]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + + class _BaseDeleteProductInput: + def __hash__(self): # pragma: NO COVER + return NotImplementedError("__hash__ must be implemented.") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = { + "dataSource": "", + } + + @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": "delete", + "uri": "/products/v1/{name=accounts/*/productInputs/*}", + }, + ] + return http_options + + @staticmethod + def _get_transcoded_request(http_options, request): + pb_request = productinputs.DeleteProductInputRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + return transcoded_request + + @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( + _BaseProductInputsServiceRestTransport._BaseDeleteProductInput._get_unset_required_fields( + query_params + ) + ) + + query_params["$alt"] = "json;enum-encoding=int" + return query_params + + class _BaseInsertProductInput: + def __hash__(self): # pragma: NO COVER + return NotImplementedError("__hash__ must be implemented.") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = { + "dataSource": "", + } + + @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": "post", + "uri": "/products/v1/{parent=accounts/*}/productInputs:insert", + "body": "product_input", + }, + ] + return http_options + + @staticmethod + def _get_transcoded_request(http_options, request): + pb_request = productinputs.InsertProductInputRequest.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( + _BaseProductInputsServiceRestTransport._BaseInsertProductInput._get_unset_required_fields( + query_params + ) + ) + + query_params["$alt"] = "json;enum-encoding=int" + return query_params + + class _BaseUpdateProductInput: + def __hash__(self): # pragma: NO COVER + return NotImplementedError("__hash__ must be implemented.") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = { + "dataSource": "", + } + + @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": "/products/v1/{product_input.name=accounts/*/productInputs/*}", + "body": "product_input", + }, + ] + return http_options + + @staticmethod + def _get_transcoded_request(http_options, request): + pb_request = productinputs.UpdateProductInputRequest.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( + _BaseProductInputsServiceRestTransport._BaseUpdateProductInput._get_unset_required_fields( + query_params + ) + ) + + query_params["$alt"] = "json;enum-encoding=int" + return query_params + + +__all__ = ("_BaseProductInputsServiceRestTransport",) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/__init__.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/__init__.py new file mode 100644 index 000000000000..bff0b432a505 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/__init__.py @@ -0,0 +1,22 @@ +# -*- 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 .async_client import ProductsServiceAsyncClient +from .client import ProductsServiceClient + +__all__ = ( + "ProductsServiceClient", + "ProductsServiceAsyncClient", +) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/async_client.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/async_client.py new file mode 100644 index 000000000000..44821fd1e849 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/async_client.py @@ -0,0 +1,580 @@ +# -*- 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 collections import OrderedDict +import logging as std_logging +import re +from typing import ( + Callable, + Dict, + Mapping, + MutableMapping, + MutableSequence, + Optional, + Sequence, + Tuple, + Type, + Union, +) + +from google.api_core import exceptions as core_exceptions +from google.api_core import gapic_v1 +from google.api_core import retry_async as retries +from google.api_core.client_options import ClientOptions +from google.auth import credentials as ga_credentials # type: ignore +from google.oauth2 import service_account # type: ignore +import google.protobuf + +from google.shopping.merchant_products_v1 import gapic_version as package_version + +try: + OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.AsyncRetry, object, None] # type: ignore + +from google.shopping.type.types import types + +from google.shopping.merchant_products_v1.services.products_service import pagers +from google.shopping.merchant_products_v1.types import products, products_common + +from .client import ProductsServiceClient +from .transports.base import DEFAULT_CLIENT_INFO, ProductsServiceTransport +from .transports.grpc_asyncio import ProductsServiceGrpcAsyncIOTransport + +try: + from google.api_core import client_logging # type: ignore + + CLIENT_LOGGING_SUPPORTED = True # pragma: NO COVER +except ImportError: # pragma: NO COVER + CLIENT_LOGGING_SUPPORTED = False + +_LOGGER = std_logging.getLogger(__name__) + + +class ProductsServiceAsyncClient: + """Service to use Product resource.""" + + _client: ProductsServiceClient + + # Copy defaults from the synchronous client for use here. + # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead. + DEFAULT_ENDPOINT = ProductsServiceClient.DEFAULT_ENDPOINT + DEFAULT_MTLS_ENDPOINT = ProductsServiceClient.DEFAULT_MTLS_ENDPOINT + _DEFAULT_ENDPOINT_TEMPLATE = ProductsServiceClient._DEFAULT_ENDPOINT_TEMPLATE + _DEFAULT_UNIVERSE = ProductsServiceClient._DEFAULT_UNIVERSE + + product_path = staticmethod(ProductsServiceClient.product_path) + parse_product_path = staticmethod(ProductsServiceClient.parse_product_path) + common_billing_account_path = staticmethod( + ProductsServiceClient.common_billing_account_path + ) + parse_common_billing_account_path = staticmethod( + ProductsServiceClient.parse_common_billing_account_path + ) + common_folder_path = staticmethod(ProductsServiceClient.common_folder_path) + parse_common_folder_path = staticmethod( + ProductsServiceClient.parse_common_folder_path + ) + common_organization_path = staticmethod( + ProductsServiceClient.common_organization_path + ) + parse_common_organization_path = staticmethod( + ProductsServiceClient.parse_common_organization_path + ) + common_project_path = staticmethod(ProductsServiceClient.common_project_path) + parse_common_project_path = staticmethod( + ProductsServiceClient.parse_common_project_path + ) + common_location_path = staticmethod(ProductsServiceClient.common_location_path) + parse_common_location_path = staticmethod( + ProductsServiceClient.parse_common_location_path + ) + + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials + info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + ProductsServiceAsyncClient: The constructed client. + """ + return ProductsServiceClient.from_service_account_info.__func__(ProductsServiceAsyncClient, info, *args, **kwargs) # type: ignore + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + ProductsServiceAsyncClient: The constructed client. + """ + return ProductsServiceClient.from_service_account_file.__func__(ProductsServiceAsyncClient, filename, *args, **kwargs) # type: ignore + + from_service_account_json = from_service_account_file + + @classmethod + def get_mtls_endpoint_and_cert_source( + cls, client_options: Optional[ClientOptions] = None + ): + """Return the API endpoint and client cert source for mutual TLS. + + The client cert source is determined in the following order: + (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the + client cert source is None. + (2) if `client_options.client_cert_source` is provided, use the provided one; if the + default client cert source exists, use the default one; otherwise the client cert + source is None. + + The API endpoint is determined in the following order: + (1) if `client_options.api_endpoint` if provided, use the provided one. + (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the + default mTLS endpoint; if the environment variable is "never", use the default API + endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise + use the default API endpoint. + + More details can be found at https://google.aip.dev/auth/4114. + + Args: + client_options (google.api_core.client_options.ClientOptions): Custom options for the + client. Only the `api_endpoint` and `client_cert_source` properties may be used + in this method. + + Returns: + Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the + client cert source to use. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If any errors happen. + """ + return ProductsServiceClient.get_mtls_endpoint_and_cert_source(client_options) # type: ignore + + @property + def transport(self) -> ProductsServiceTransport: + """Returns the transport used by the client instance. + + Returns: + ProductsServiceTransport: The transport used by the client instance. + """ + return self._client.transport + + @property + def api_endpoint(self): + """Return the API endpoint used by the client instance. + + Returns: + str: The API endpoint used by the client instance. + """ + return self._client._api_endpoint + + @property + def universe_domain(self) -> str: + """Return the universe domain used by the client instance. + + Returns: + str: The universe domain used + by the client instance. + """ + return self._client._universe_domain + + get_transport_class = ProductsServiceClient.get_transport_class + + def __init__( + self, + *, + credentials: Optional[ga_credentials.Credentials] = None, + transport: Optional[ + Union[ + str, ProductsServiceTransport, Callable[..., ProductsServiceTransport] + ] + ] = "grpc_asyncio", + client_options: Optional[ClientOptions] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiates the products service async client. + + Args: + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + transport (Optional[Union[str,ProductsServiceTransport,Callable[..., ProductsServiceTransport]]]): + The transport to use, or a Callable that constructs and returns a new transport to use. + If a Callable is given, it will be called with the same set of initialization + arguments as used in the ProductsServiceTransport constructor. + If set to None, a transport is chosen automatically. + client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]): + Custom options for the client. + + 1. The ``api_endpoint`` property can be used to override the + default endpoint provided by the client when ``transport`` is + not explicitly provided. Only if this property is not set and + ``transport`` was not explicitly provided, the endpoint is + determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment + variable, which have one of the following values: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint) and "auto" (auto-switch to the + default mTLS endpoint if client certificate is present; this is + the default value). + + 2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide a client certificate for mTLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + + 3. The ``universe_domain`` property can be used to override the + default "googleapis.com" universe. Note that ``api_endpoint`` + property still takes precedence; and ``universe_domain`` is + currently not supported for mTLS. + + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + """ + self._client = ProductsServiceClient( + credentials=credentials, + transport=transport, + client_options=client_options, + client_info=client_info, + ) + + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + std_logging.DEBUG + ): # pragma: NO COVER + _LOGGER.debug( + "Created client `google.shopping.merchant.products_v1.ProductsServiceAsyncClient`.", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductsService", + "universeDomain": getattr( + self._client._transport._credentials, "universe_domain", "" + ), + "credentialsType": f"{type(self._client._transport._credentials).__module__}.{type(self._client._transport._credentials).__qualname__}", + "credentialsInfo": getattr( + self.transport._credentials, "get_cred_info", lambda: None + )(), + } + if hasattr(self._client._transport, "_credentials") + else { + "serviceName": "google.shopping.merchant.products.v1.ProductsService", + "credentialsType": None, + }, + ) + + async def get_product( + self, + request: Optional[Union[products.GetProductRequest, dict]] = None, + *, + name: Optional[str] = None, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> products.Product: + r"""Retrieves the processed product from your Merchant + Center account. + After inserting, updating, or deleting a product input, + it may take several minutes before the updated final + product can be retrieved. + + .. 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.shopping import merchant_products_v1 + + async def sample_get_product(): + # Create a client + client = merchant_products_v1.ProductsServiceAsyncClient() + + # Initialize request argument(s) + request = merchant_products_v1.GetProductRequest( + name="name_value", + ) + + # Make the request + response = await client.get_product(request=request) + + # Handle the response + print(response) + + Args: + request (Optional[Union[google.shopping.merchant_products_v1.types.GetProductRequest, dict]]): + The request object. Request message for the GetProduct + method. + name (:class:`str`): + Required. The name of the product to retrieve. Format: + ``accounts/{account}/products/{product}`` where the last + section ``product`` consists of: + ``content_language~feed_label~offer_id`` example for + product name is ``accounts/123/products/en~US~sku123``. + A legacy local product name would be + ``accounts/123/products/local~en~US~sku123``. Note: For + calls to the v1beta version, the ``product`` section + consists of: + ``channel~content_language~feed_label~offer_id``, for + example: ``accounts/123/products/online~en~US~sku123``. + + This corresponds to the ``name`` 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.shopping.merchant_products_v1.types.Product: + The processed product, built from multiple [product + inputs][google.shopping.merchant.products.v1main.ProductInput] + after applying rules and supplemental data sources. + This processed product matches what is shown in your + Merchant Center account. Each product is built from + exactly one primary data source product input, and + multiple supplemental data source inputs. After + inserting, updating, or deleting a product input, it + may take several minutes before the updated processed + product can be retrieved. + + All fields in the processed product and its + sub-messages match the name of their corresponding + attribute in the [Product data + specification](\ https://support.google.com/merchants/answer/7052112) + with some exceptions. + + """ + # 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 = [name] + 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, products.GetProductRequest): + request = products.GetProductRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._client._transport._wrapped_methods[ + self._client._transport.get_product + ] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Validate the universe domain. + self._client._validate_universe_domain() + + # Send the request. + response = await rpc( + request, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Done; return the response. + return response + + async def list_products( + self, + request: Optional[Union[products.ListProductsRequest, dict]] = None, + *, + parent: Optional[str] = None, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> pagers.ListProductsAsyncPager: + r"""Lists the processed products in your Merchant Center account. + The response might contain fewer items than specified by + ``pageSize``. Rely on ``pageToken`` to determine if there are + more items to be requested. + + After inserting, updating, or deleting a product input, it may + take several minutes before the updated processed product can be + retrieved. + + .. 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.shopping import merchant_products_v1 + + async def sample_list_products(): + # Create a client + client = merchant_products_v1.ProductsServiceAsyncClient() + + # Initialize request argument(s) + request = merchant_products_v1.ListProductsRequest( + parent="parent_value", + ) + + # Make the request + page_result = client.list_products(request=request) + + # Handle the response + async for response in page_result: + print(response) + + Args: + request (Optional[Union[google.shopping.merchant_products_v1.types.ListProductsRequest, dict]]): + The request object. Request message for the ListProducts + method. + parent (:class:`str`): + Required. The account to list processed products for. + Format: ``accounts/{account}`` + + This corresponds to the ``parent`` 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.shopping.merchant_products_v1.services.products_service.pagers.ListProductsAsyncPager: + Response message for the ListProducts + method. + Iterating over this object will yield + results and resolve additional pages + automatically. + + """ + # 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 = [parent] + 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, products.ListProductsRequest): + request = products.ListProductsRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + if parent is not None: + request.parent = parent + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._client._transport._wrapped_methods[ + self._client._transport.list_products + ] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Validate the universe domain. + self._client._validate_universe_domain() + + # Send the request. + response = await rpc( + request, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # This method is paged; wrap the response in a pager, which provides + # an `__aiter__` convenience method. + response = pagers.ListProductsAsyncPager( + method=rpc, + request=request, + response=response, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Done; return the response. + return response + + async def __aenter__(self) -> "ProductsServiceAsyncClient": + return self + + async def __aexit__(self, exc_type, exc, tb): + await self.transport.close() + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=package_version.__version__ +) + +if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"): # pragma: NO COVER + DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__ + + +__all__ = ("ProductsServiceAsyncClient",) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/client.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/client.py new file mode 100644 index 000000000000..4ff113915733 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/client.py @@ -0,0 +1,998 @@ +# -*- 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 collections import OrderedDict +from http import HTTPStatus +import json +import logging as std_logging +import os +import re +from typing import ( + Callable, + Dict, + Mapping, + MutableMapping, + MutableSequence, + Optional, + Sequence, + Tuple, + Type, + Union, + cast, +) +import warnings + +from google.api_core import client_options as client_options_lib +from google.api_core import exceptions as core_exceptions +from google.api_core import gapic_v1 +from google.api_core import retry as retries +from google.auth import credentials as ga_credentials # type: ignore +from google.auth.exceptions import MutualTLSChannelError # type: ignore +from google.auth.transport import mtls # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.oauth2 import service_account # type: ignore +import google.protobuf + +from google.shopping.merchant_products_v1 import gapic_version as package_version + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object, None] # type: ignore + +try: + from google.api_core import client_logging # type: ignore + + CLIENT_LOGGING_SUPPORTED = True # pragma: NO COVER +except ImportError: # pragma: NO COVER + CLIENT_LOGGING_SUPPORTED = False + +_LOGGER = std_logging.getLogger(__name__) + +from google.shopping.type.types import types + +from google.shopping.merchant_products_v1.services.products_service import pagers +from google.shopping.merchant_products_v1.types import products, products_common + +from .transports.base import DEFAULT_CLIENT_INFO, ProductsServiceTransport +from .transports.grpc import ProductsServiceGrpcTransport +from .transports.grpc_asyncio import ProductsServiceGrpcAsyncIOTransport +from .transports.rest import ProductsServiceRestTransport + + +class ProductsServiceClientMeta(type): + """Metaclass for the ProductsService client. + + This provides class-level methods for building and retrieving + support objects (e.g. transport) without polluting the client instance + objects. + """ + + _transport_registry = ( + OrderedDict() + ) # type: Dict[str, Type[ProductsServiceTransport]] + _transport_registry["grpc"] = ProductsServiceGrpcTransport + _transport_registry["grpc_asyncio"] = ProductsServiceGrpcAsyncIOTransport + _transport_registry["rest"] = ProductsServiceRestTransport + + def get_transport_class( + cls, + label: Optional[str] = None, + ) -> Type[ProductsServiceTransport]: + """Returns an appropriate transport class. + + Args: + label: The name of the desired transport. If none is + provided, then the first transport in the registry is used. + + Returns: + The transport class to use. + """ + # If a specific transport is requested, return that one. + if label: + return cls._transport_registry[label] + + # No transport is requested; return the default (that is, the first one + # in the dictionary). + return next(iter(cls._transport_registry.values())) + + +class ProductsServiceClient(metaclass=ProductsServiceClientMeta): + """Service to use Product resource.""" + + @staticmethod + def _get_default_mtls_endpoint(api_endpoint): + """Converts api endpoint to mTLS endpoint. + + Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to + "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively. + Args: + api_endpoint (Optional[str]): the api endpoint to convert. + Returns: + str: converted mTLS api endpoint. + """ + if not api_endpoint: + return api_endpoint + + mtls_endpoint_re = re.compile( + r"(?P[^.]+)(?P\.mtls)?(?P\.sandbox)?(?P\.googleapis\.com)?" + ) + + m = mtls_endpoint_re.match(api_endpoint) + name, mtls, sandbox, googledomain = m.groups() + if mtls or not googledomain: + return api_endpoint + + if sandbox: + return api_endpoint.replace( + "sandbox.googleapis.com", "mtls.sandbox.googleapis.com" + ) + + return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com") + + # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead. + DEFAULT_ENDPOINT = "merchantapi.googleapis.com" + DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__( # type: ignore + DEFAULT_ENDPOINT + ) + + _DEFAULT_ENDPOINT_TEMPLATE = "merchantapi.{UNIVERSE_DOMAIN}" + _DEFAULT_UNIVERSE = "googleapis.com" + + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials + info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + ProductsServiceClient: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_info(info) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + ProductsServiceClient: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_file(filename) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + + from_service_account_json = from_service_account_file + + @property + def transport(self) -> ProductsServiceTransport: + """Returns the transport used by the client instance. + + Returns: + ProductsServiceTransport: The transport used by the client + instance. + """ + return self._transport + + @staticmethod + def product_path( + account: str, + product: str, + ) -> str: + """Returns a fully-qualified product string.""" + return "accounts/{account}/products/{product}".format( + account=account, + product=product, + ) + + @staticmethod + def parse_product_path(path: str) -> Dict[str, str]: + """Parses a product path into its component segments.""" + m = re.match(r"^accounts/(?P.+?)/products/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_billing_account_path( + billing_account: str, + ) -> str: + """Returns a fully-qualified billing_account string.""" + return "billingAccounts/{billing_account}".format( + billing_account=billing_account, + ) + + @staticmethod + def parse_common_billing_account_path(path: str) -> Dict[str, str]: + """Parse a billing_account path into its component segments.""" + m = re.match(r"^billingAccounts/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_folder_path( + folder: str, + ) -> str: + """Returns a fully-qualified folder string.""" + return "folders/{folder}".format( + folder=folder, + ) + + @staticmethod + def parse_common_folder_path(path: str) -> Dict[str, str]: + """Parse a folder path into its component segments.""" + m = re.match(r"^folders/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_organization_path( + organization: str, + ) -> str: + """Returns a fully-qualified organization string.""" + return "organizations/{organization}".format( + organization=organization, + ) + + @staticmethod + def parse_common_organization_path(path: str) -> Dict[str, str]: + """Parse a organization path into its component segments.""" + m = re.match(r"^organizations/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_project_path( + project: str, + ) -> str: + """Returns a fully-qualified project string.""" + return "projects/{project}".format( + project=project, + ) + + @staticmethod + def parse_common_project_path(path: str) -> Dict[str, str]: + """Parse a project path into its component segments.""" + m = re.match(r"^projects/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_location_path( + project: str, + location: str, + ) -> str: + """Returns a fully-qualified location string.""" + return "projects/{project}/locations/{location}".format( + project=project, + location=location, + ) + + @staticmethod + def parse_common_location_path(path: str) -> Dict[str, str]: + """Parse a location path into its component segments.""" + m = re.match(r"^projects/(?P.+?)/locations/(?P.+?)$", path) + return m.groupdict() if m else {} + + @classmethod + def get_mtls_endpoint_and_cert_source( + cls, client_options: Optional[client_options_lib.ClientOptions] = None + ): + """Deprecated. Return the API endpoint and client cert source for mutual TLS. + + The client cert source is determined in the following order: + (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the + client cert source is None. + (2) if `client_options.client_cert_source` is provided, use the provided one; if the + default client cert source exists, use the default one; otherwise the client cert + source is None. + + The API endpoint is determined in the following order: + (1) if `client_options.api_endpoint` if provided, use the provided one. + (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the + default mTLS endpoint; if the environment variable is "never", use the default API + endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise + use the default API endpoint. + + More details can be found at https://google.aip.dev/auth/4114. + + Args: + client_options (google.api_core.client_options.ClientOptions): Custom options for the + client. Only the `api_endpoint` and `client_cert_source` properties may be used + in this method. + + Returns: + Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the + client cert source to use. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If any errors happen. + """ + + warnings.warn( + "get_mtls_endpoint_and_cert_source is deprecated. Use the api_endpoint property instead.", + DeprecationWarning, + ) + if client_options is None: + client_options = client_options_lib.ClientOptions() + use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") + if use_client_cert not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) + if use_mtls_endpoint not in ("auto", "never", "always"): + raise MutualTLSChannelError( + "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" + ) + + # Figure out the client cert source to use. + client_cert_source = None + if use_client_cert == "true": + if client_options.client_cert_source: + client_cert_source = client_options.client_cert_source + elif mtls.has_default_client_cert_source(): + client_cert_source = mtls.default_client_cert_source() + + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + elif use_mtls_endpoint == "always" or ( + use_mtls_endpoint == "auto" and client_cert_source + ): + api_endpoint = cls.DEFAULT_MTLS_ENDPOINT + else: + api_endpoint = cls.DEFAULT_ENDPOINT + + return api_endpoint, client_cert_source + + @staticmethod + def _read_environment_variables(): + """Returns the environment variables used by the client. + + Returns: + Tuple[bool, str, str]: returns the GOOGLE_API_USE_CLIENT_CERTIFICATE, + GOOGLE_API_USE_MTLS_ENDPOINT, and GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variables. + + Raises: + ValueError: If GOOGLE_API_USE_CLIENT_CERTIFICATE is not + any of ["true", "false"]. + google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT + is not any of ["auto", "never", "always"]. + """ + use_client_cert = os.getenv( + "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false" + ).lower() + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() + universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") + if use_client_cert not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) + if use_mtls_endpoint not in ("auto", "never", "always"): + raise MutualTLSChannelError( + "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" + ) + return use_client_cert == "true", use_mtls_endpoint, universe_domain_env + + @staticmethod + def _get_client_cert_source(provided_cert_source, use_cert_flag): + """Return the client cert source to be used by the client. + + Args: + provided_cert_source (bytes): The client certificate source provided. + use_cert_flag (bool): A flag indicating whether to use the client certificate. + + Returns: + bytes or None: The client cert source to be used by the client. + """ + client_cert_source = None + if use_cert_flag: + if provided_cert_source: + client_cert_source = provided_cert_source + elif mtls.has_default_client_cert_source(): + client_cert_source = mtls.default_client_cert_source() + return client_cert_source + + @staticmethod + def _get_api_endpoint( + api_override, client_cert_source, universe_domain, use_mtls_endpoint + ): + """Return the API endpoint used by the client. + + Args: + api_override (str): The API endpoint override. If specified, this is always + the return value of this function and the other arguments are not used. + client_cert_source (bytes): The client certificate source used by the client. + universe_domain (str): The universe domain used by the client. + use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters. + Possible values are "always", "auto", or "never". + + Returns: + str: The API endpoint to be used by the client. + """ + if api_override is not None: + api_endpoint = api_override + elif use_mtls_endpoint == "always" or ( + use_mtls_endpoint == "auto" and client_cert_source + ): + _default_universe = ProductsServiceClient._DEFAULT_UNIVERSE + if universe_domain != _default_universe: + raise MutualTLSChannelError( + f"mTLS is not supported in any universe other than {_default_universe}." + ) + api_endpoint = ProductsServiceClient.DEFAULT_MTLS_ENDPOINT + else: + api_endpoint = ProductsServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=universe_domain + ) + return api_endpoint + + @staticmethod + def _get_universe_domain( + client_universe_domain: Optional[str], universe_domain_env: Optional[str] + ) -> str: + """Return the universe domain used by the client. + + Args: + client_universe_domain (Optional[str]): The universe domain configured via the client options. + universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable. + + Returns: + str: The universe domain to be used by the client. + + Raises: + ValueError: If the universe domain is an empty string. + """ + universe_domain = ProductsServiceClient._DEFAULT_UNIVERSE + if client_universe_domain is not None: + universe_domain = client_universe_domain + elif universe_domain_env is not None: + universe_domain = universe_domain_env + if len(universe_domain.strip()) == 0: + raise ValueError("Universe Domain cannot be an empty string.") + return universe_domain + + def _validate_universe_domain(self): + """Validates client's and credentials' universe domains are consistent. + + Returns: + bool: True iff the configured universe domain is valid. + + Raises: + ValueError: If the configured universe domain is not valid. + """ + + # NOTE (b/349488459): universe validation is disabled until further notice. + return True + + def _add_cred_info_for_auth_errors( + self, error: core_exceptions.GoogleAPICallError + ) -> None: + """Adds credential info string to error details for 401/403/404 errors. + + Args: + error (google.api_core.exceptions.GoogleAPICallError): The error to add the cred info. + """ + if error.code not in [ + HTTPStatus.UNAUTHORIZED, + HTTPStatus.FORBIDDEN, + HTTPStatus.NOT_FOUND, + ]: + return + + cred = self._transport._credentials + + # get_cred_info is only available in google-auth>=2.35.0 + if not hasattr(cred, "get_cred_info"): + return + + # ignore the type check since pypy test fails when get_cred_info + # is not available + cred_info = cred.get_cred_info() # type: ignore + if cred_info and hasattr(error._details, "append"): + error._details.append(json.dumps(cred_info)) + + @property + def api_endpoint(self): + """Return the API endpoint used by the client instance. + + Returns: + str: The API endpoint used by the client instance. + """ + return self._api_endpoint + + @property + def universe_domain(self) -> str: + """Return the universe domain used by the client instance. + + Returns: + str: The universe domain used by the client instance. + """ + return self._universe_domain + + def __init__( + self, + *, + credentials: Optional[ga_credentials.Credentials] = None, + transport: Optional[ + Union[ + str, ProductsServiceTransport, Callable[..., ProductsServiceTransport] + ] + ] = None, + client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiates the products service client. + + Args: + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + transport (Optional[Union[str,ProductsServiceTransport,Callable[..., ProductsServiceTransport]]]): + The transport to use, or a Callable that constructs and returns a new transport. + If a Callable is given, it will be called with the same set of initialization + arguments as used in the ProductsServiceTransport constructor. + If set to None, a transport is chosen automatically. + client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]): + Custom options for the client. + + 1. The ``api_endpoint`` property can be used to override the + default endpoint provided by the client when ``transport`` is + not explicitly provided. Only if this property is not set and + ``transport`` was not explicitly provided, the endpoint is + determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment + variable, which have one of the following values: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint) and "auto" (auto-switch to the + default mTLS endpoint if client certificate is present; this is + the default value). + + 2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide a client certificate for mTLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + + 3. The ``universe_domain`` property can be used to override the + default "googleapis.com" universe. Note that the ``api_endpoint`` + property still takes precedence; and ``universe_domain`` is + currently not supported for mTLS. + + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport + creation failed for any reason. + """ + self._client_options = client_options + if isinstance(self._client_options, dict): + self._client_options = client_options_lib.from_dict(self._client_options) + if self._client_options is None: + self._client_options = client_options_lib.ClientOptions() + self._client_options = cast( + client_options_lib.ClientOptions, self._client_options + ) + + universe_domain_opt = getattr(self._client_options, "universe_domain", None) + + ( + self._use_client_cert, + self._use_mtls_endpoint, + self._universe_domain_env, + ) = ProductsServiceClient._read_environment_variables() + self._client_cert_source = ProductsServiceClient._get_client_cert_source( + self._client_options.client_cert_source, self._use_client_cert + ) + self._universe_domain = ProductsServiceClient._get_universe_domain( + universe_domain_opt, self._universe_domain_env + ) + self._api_endpoint = None # updated below, depending on `transport` + + # Initialize the universe domain validation. + self._is_universe_domain_valid = False + + if CLIENT_LOGGING_SUPPORTED: # pragma: NO COVER + # Setup logging. + client_logging.initialize_logging() + + api_key_value = getattr(self._client_options, "api_key", None) + if api_key_value and credentials: + raise ValueError( + "client_options.api_key and credentials are mutually exclusive" + ) + + # Save or instantiate the transport. + # Ordinarily, we provide the transport, but allowing a custom transport + # instance provides an extensibility point for unusual situations. + transport_provided = isinstance(transport, ProductsServiceTransport) + if transport_provided: + # transport is a ProductsServiceTransport instance. + if credentials or self._client_options.credentials_file or api_key_value: + raise ValueError( + "When providing a transport instance, " + "provide its credentials directly." + ) + if self._client_options.scopes: + raise ValueError( + "When providing a transport instance, provide its scopes " + "directly." + ) + self._transport = cast(ProductsServiceTransport, transport) + self._api_endpoint = self._transport.host + + self._api_endpoint = ( + self._api_endpoint + or ProductsServiceClient._get_api_endpoint( + self._client_options.api_endpoint, + self._client_cert_source, + self._universe_domain, + self._use_mtls_endpoint, + ) + ) + + if not transport_provided: + import google.auth._default # type: ignore + + if api_key_value and hasattr( + google.auth._default, "get_api_key_credentials" + ): + credentials = google.auth._default.get_api_key_credentials( + api_key_value + ) + + transport_init: Union[ + Type[ProductsServiceTransport], Callable[..., ProductsServiceTransport] + ] = ( + ProductsServiceClient.get_transport_class(transport) + if isinstance(transport, str) or transport is None + else cast(Callable[..., ProductsServiceTransport], transport) + ) + # initialize with the provided callable or the passed in class + self._transport = transport_init( + credentials=credentials, + credentials_file=self._client_options.credentials_file, + host=self._api_endpoint, + scopes=self._client_options.scopes, + client_cert_source_for_mtls=self._client_cert_source, + quota_project_id=self._client_options.quota_project_id, + client_info=client_info, + always_use_jwt_access=True, + api_audience=self._client_options.api_audience, + ) + + if "async" not in str(self._transport): + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + std_logging.DEBUG + ): # pragma: NO COVER + _LOGGER.debug( + "Created client `google.shopping.merchant.products_v1.ProductsServiceClient`.", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductsService", + "universeDomain": getattr( + self._transport._credentials, "universe_domain", "" + ), + "credentialsType": f"{type(self._transport._credentials).__module__}.{type(self._transport._credentials).__qualname__}", + "credentialsInfo": getattr( + self.transport._credentials, "get_cred_info", lambda: None + )(), + } + if hasattr(self._transport, "_credentials") + else { + "serviceName": "google.shopping.merchant.products.v1.ProductsService", + "credentialsType": None, + }, + ) + + def get_product( + self, + request: Optional[Union[products.GetProductRequest, dict]] = None, + *, + name: Optional[str] = None, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> products.Product: + r"""Retrieves the processed product from your Merchant + Center account. + After inserting, updating, or deleting a product input, + it may take several minutes before the updated final + product can be retrieved. + + .. 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.shopping import merchant_products_v1 + + def sample_get_product(): + # Create a client + client = merchant_products_v1.ProductsServiceClient() + + # Initialize request argument(s) + request = merchant_products_v1.GetProductRequest( + name="name_value", + ) + + # Make the request + response = client.get_product(request=request) + + # Handle the response + print(response) + + Args: + request (Union[google.shopping.merchant_products_v1.types.GetProductRequest, dict]): + The request object. Request message for the GetProduct + method. + name (str): + Required. The name of the product to retrieve. Format: + ``accounts/{account}/products/{product}`` where the last + section ``product`` consists of: + ``content_language~feed_label~offer_id`` example for + product name is ``accounts/123/products/en~US~sku123``. + A legacy local product name would be + ``accounts/123/products/local~en~US~sku123``. Note: For + calls to the v1beta version, the ``product`` section + consists of: + ``channel~content_language~feed_label~offer_id``, for + example: ``accounts/123/products/online~en~US~sku123``. + + This corresponds to the ``name`` 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.shopping.merchant_products_v1.types.Product: + The processed product, built from multiple [product + inputs][google.shopping.merchant.products.v1main.ProductInput] + after applying rules and supplemental data sources. + This processed product matches what is shown in your + Merchant Center account. Each product is built from + exactly one primary data source product input, and + multiple supplemental data source inputs. After + inserting, updating, or deleting a product input, it + may take several minutes before the updated processed + product can be retrieved. + + All fields in the processed product and its + sub-messages match the name of their corresponding + attribute in the [Product data + specification](\ https://support.google.com/merchants/answer/7052112) + with some exceptions. + + """ + # 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 = [name] + 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, products.GetProductRequest): + request = products.GetProductRequest(request) + # If we have keyword arguments corresponding to fields on the + # request, apply these. + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.get_product] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Validate the universe domain. + self._validate_universe_domain() + + # Send the request. + response = rpc( + request, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Done; return the response. + return response + + def list_products( + self, + request: Optional[Union[products.ListProductsRequest, dict]] = None, + *, + parent: Optional[str] = None, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> pagers.ListProductsPager: + r"""Lists the processed products in your Merchant Center account. + The response might contain fewer items than specified by + ``pageSize``. Rely on ``pageToken`` to determine if there are + more items to be requested. + + After inserting, updating, or deleting a product input, it may + take several minutes before the updated processed product can be + retrieved. + + .. 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.shopping import merchant_products_v1 + + def sample_list_products(): + # Create a client + client = merchant_products_v1.ProductsServiceClient() + + # Initialize request argument(s) + request = merchant_products_v1.ListProductsRequest( + parent="parent_value", + ) + + # Make the request + page_result = client.list_products(request=request) + + # Handle the response + for response in page_result: + print(response) + + Args: + request (Union[google.shopping.merchant_products_v1.types.ListProductsRequest, dict]): + The request object. Request message for the ListProducts + method. + parent (str): + Required. The account to list processed products for. + Format: ``accounts/{account}`` + + This corresponds to the ``parent`` 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.shopping.merchant_products_v1.services.products_service.pagers.ListProductsPager: + Response message for the ListProducts + method. + Iterating over this object will yield + results and resolve additional pages + automatically. + + """ + # 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 = [parent] + 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, products.ListProductsRequest): + request = products.ListProductsRequest(request) + # If we have keyword arguments corresponding to fields on the + # request, apply these. + if parent is not None: + request.parent = parent + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.list_products] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Validate the universe domain. + self._validate_universe_domain() + + # Send the request. + response = rpc( + request, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # This method is paged; wrap the response in a pager, which provides + # an `__iter__` convenience method. + response = pagers.ListProductsPager( + method=rpc, + request=request, + response=response, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Done; return the response. + return response + + def __enter__(self) -> "ProductsServiceClient": + return self + + def __exit__(self, type, value, traceback): + """Releases underlying transport's resources. + + .. warning:: + ONLY use as a context manager if the transport is NOT shared + with other clients! Exiting the with block will CLOSE the transport + and may cause errors in other clients! + """ + self.transport.close() + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=package_version.__version__ +) + +if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"): # pragma: NO COVER + DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__ + +__all__ = ("ProductsServiceClient",) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/pagers.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/pagers.py new file mode 100644 index 000000000000..02684eca9288 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/pagers.py @@ -0,0 +1,197 @@ +# -*- 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 typing import ( + Any, + AsyncIterator, + Awaitable, + Callable, + Iterator, + Optional, + Sequence, + Tuple, + Union, +) + +from google.api_core import gapic_v1 +from google.api_core import retry as retries +from google.api_core import retry_async as retries_async + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None] + OptionalAsyncRetry = Union[ + retries_async.AsyncRetry, gapic_v1.method._MethodDefault, None + ] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object, None] # type: ignore + OptionalAsyncRetry = Union[retries_async.AsyncRetry, object, None] # type: ignore + +from google.shopping.merchant_products_v1.types import products + + +class ListProductsPager: + """A pager for iterating through ``list_products`` requests. + + This class thinly wraps an initial + :class:`google.shopping.merchant_products_v1.types.ListProductsResponse` object, and + provides an ``__iter__`` method to iterate through its + ``products`` field. + + If there are more pages, the ``__iter__`` method will make additional + ``ListProducts`` requests and continue to iterate + through the ``products`` field on the + corresponding responses. + + All the usual :class:`google.shopping.merchant_products_v1.types.ListProductsResponse` + attributes are available on the pager. If multiple requests are made, only + the most recent response is retained, and thus used for attribute lookup. + """ + + def __init__( + self, + method: Callable[..., products.ListProductsResponse], + request: products.ListProductsRequest, + response: products.ListProductsResponse, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = () + ): + """Instantiate the pager. + + Args: + method (Callable): The method that was originally called, and + which instantiated this pager. + request (google.shopping.merchant_products_v1.types.ListProductsRequest): + The initial request object. + response (google.shopping.merchant_products_v1.types.ListProductsResponse): + The initial response object. + 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`. + """ + self._method = method + self._request = products.ListProductsRequest(request) + self._response = response + self._retry = retry + self._timeout = timeout + self._metadata = metadata + + def __getattr__(self, name: str) -> Any: + return getattr(self._response, name) + + @property + def pages(self) -> Iterator[products.ListProductsResponse]: + yield self._response + while self._response.next_page_token: + self._request.page_token = self._response.next_page_token + self._response = self._method( + self._request, + retry=self._retry, + timeout=self._timeout, + metadata=self._metadata, + ) + yield self._response + + def __iter__(self) -> Iterator[products.Product]: + for page in self.pages: + yield from page.products + + def __repr__(self) -> str: + return "{0}<{1!r}>".format(self.__class__.__name__, self._response) + + +class ListProductsAsyncPager: + """A pager for iterating through ``list_products`` requests. + + This class thinly wraps an initial + :class:`google.shopping.merchant_products_v1.types.ListProductsResponse` object, and + provides an ``__aiter__`` method to iterate through its + ``products`` field. + + If there are more pages, the ``__aiter__`` method will make additional + ``ListProducts`` requests and continue to iterate + through the ``products`` field on the + corresponding responses. + + All the usual :class:`google.shopping.merchant_products_v1.types.ListProductsResponse` + attributes are available on the pager. If multiple requests are made, only + the most recent response is retained, and thus used for attribute lookup. + """ + + def __init__( + self, + method: Callable[..., Awaitable[products.ListProductsResponse]], + request: products.ListProductsRequest, + response: products.ListProductsResponse, + *, + retry: OptionalAsyncRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = () + ): + """Instantiates the pager. + + Args: + method (Callable): The method that was originally called, and + which instantiated this pager. + request (google.shopping.merchant_products_v1.types.ListProductsRequest): + The initial request object. + response (google.shopping.merchant_products_v1.types.ListProductsResponse): + The initial response object. + retry (google.api_core.retry.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`. + """ + self._method = method + self._request = products.ListProductsRequest(request) + self._response = response + self._retry = retry + self._timeout = timeout + self._metadata = metadata + + def __getattr__(self, name: str) -> Any: + return getattr(self._response, name) + + @property + async def pages(self) -> AsyncIterator[products.ListProductsResponse]: + yield self._response + while self._response.next_page_token: + self._request.page_token = self._response.next_page_token + self._response = await self._method( + self._request, + retry=self._retry, + timeout=self._timeout, + metadata=self._metadata, + ) + yield self._response + + def __aiter__(self) -> AsyncIterator[products.Product]: + async def async_generator(): + async for page in self.pages: + for response in page.products: + yield response + + return async_generator() + + def __repr__(self) -> str: + return "{0}<{1!r}>".format(self.__class__.__name__, self._response) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/README.rst b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/README.rst new file mode 100644 index 000000000000..c1cbe3d98697 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/README.rst @@ -0,0 +1,9 @@ + +transport inheritance structure +_______________________________ + +`ProductsServiceTransport` is the ABC for all transports. +- public child `ProductsServiceGrpcTransport` for sync gRPC transport (defined in `grpc.py`). +- public child `ProductsServiceGrpcAsyncIOTransport` for async gRPC transport (defined in `grpc_asyncio.py`). +- private child `_BaseProductsServiceRestTransport` for base REST transport with inner classes `_BaseMETHOD` (defined in `rest_base.py`). +- public child `ProductsServiceRestTransport` for sync REST transport with inner classes `METHOD` derived from the parent's corresponding `_BaseMETHOD` classes (defined in `rest.py`). diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/__init__.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/__init__.py new file mode 100644 index 000000000000..f299eca615ee --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/__init__.py @@ -0,0 +1,36 @@ +# -*- 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 collections import OrderedDict +from typing import Dict, Type + +from .base import ProductsServiceTransport +from .grpc import ProductsServiceGrpcTransport +from .grpc_asyncio import ProductsServiceGrpcAsyncIOTransport +from .rest import ProductsServiceRestInterceptor, ProductsServiceRestTransport + +# Compile a registry of transports. +_transport_registry = OrderedDict() # type: Dict[str, Type[ProductsServiceTransport]] +_transport_registry["grpc"] = ProductsServiceGrpcTransport +_transport_registry["grpc_asyncio"] = ProductsServiceGrpcAsyncIOTransport +_transport_registry["rest"] = ProductsServiceRestTransport + +__all__ = ( + "ProductsServiceTransport", + "ProductsServiceGrpcTransport", + "ProductsServiceGrpcAsyncIOTransport", + "ProductsServiceRestTransport", + "ProductsServiceRestInterceptor", +) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/base.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/base.py new file mode 100644 index 000000000000..bff20e3e4ca7 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/base.py @@ -0,0 +1,179 @@ +# -*- 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. +# +import abc +from typing import Awaitable, Callable, Dict, Optional, Sequence, Union + +import google.api_core +from google.api_core import exceptions as core_exceptions +from google.api_core import gapic_v1 +from google.api_core import retry as retries +import google.auth # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.oauth2 import service_account # type: ignore +import google.protobuf + +from google.shopping.merchant_products_v1 import gapic_version as package_version +from google.shopping.merchant_products_v1.types import products + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=package_version.__version__ +) + +if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"): # pragma: NO COVER + DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__ + + +class ProductsServiceTransport(abc.ABC): + """Abstract transport class for ProductsService.""" + + AUTH_SCOPES = ("https://www.googleapis.com/auth/content",) + + DEFAULT_HOST: str = "merchantapi.googleapis.com" + + def __init__( + self, + *, + host: str = DEFAULT_HOST, + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + api_audience: Optional[str] = None, + **kwargs, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to (default: 'merchantapi.googleapis.com'). + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. + scopes (Optional[Sequence[str]]): A list of scopes. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + """ + + scopes_kwargs = {"scopes": scopes, "default_scopes": self.AUTH_SCOPES} + + # Save the scopes. + self._scopes = scopes + if not hasattr(self, "_ignore_credentials"): + self._ignore_credentials: bool = False + + # If no credentials are provided, then determine the appropriate + # defaults. + if credentials and credentials_file: + raise core_exceptions.DuplicateCredentialArgs( + "'credentials_file' and 'credentials' are mutually exclusive" + ) + + if credentials_file is not None: + credentials, _ = google.auth.load_credentials_from_file( + credentials_file, **scopes_kwargs, quota_project_id=quota_project_id + ) + elif credentials is None and not self._ignore_credentials: + credentials, _ = google.auth.default( + **scopes_kwargs, quota_project_id=quota_project_id + ) + # Don't apply audience if the credentials file passed from user. + if hasattr(credentials, "with_gdch_audience"): + credentials = credentials.with_gdch_audience( + api_audience if api_audience else host + ) + + # If the credentials are service account credentials, then always try to use self signed JWT. + if ( + always_use_jwt_access + and isinstance(credentials, service_account.Credentials) + and hasattr(service_account.Credentials, "with_always_use_jwt_access") + ): + credentials = credentials.with_always_use_jwt_access(True) + + # Save the credentials. + self._credentials = credentials + + # Save the hostname. Default to port 443 (HTTPS) if none is specified. + if ":" not in host: + host += ":443" + self._host = host + + @property + def host(self): + return self._host + + def _prep_wrapped_messages(self, client_info): + # Precompute the wrapped methods. + self._wrapped_methods = { + self.get_product: gapic_v1.method.wrap_method( + self.get_product, + default_timeout=None, + client_info=client_info, + ), + self.list_products: gapic_v1.method.wrap_method( + self.list_products, + default_timeout=None, + client_info=client_info, + ), + } + + def close(self): + """Closes resources associated with the transport. + + .. warning:: + Only call this method if the transport is NOT shared + with other clients - this may cause errors in other clients! + """ + raise NotImplementedError() + + @property + def get_product( + self, + ) -> Callable[ + [products.GetProductRequest], + Union[products.Product, Awaitable[products.Product]], + ]: + raise NotImplementedError() + + @property + def list_products( + self, + ) -> Callable[ + [products.ListProductsRequest], + Union[products.ListProductsResponse, Awaitable[products.ListProductsResponse]], + ]: + raise NotImplementedError() + + @property + def kind(self) -> str: + raise NotImplementedError() + + +__all__ = ("ProductsServiceTransport",) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/grpc.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/grpc.py new file mode 100644 index 000000000000..c96d0272bb43 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/grpc.py @@ -0,0 +1,391 @@ +# -*- 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. +# +import json +import logging as std_logging +import pickle +from typing import Callable, Dict, Optional, Sequence, Tuple, Union +import warnings + +from google.api_core import gapic_v1, grpc_helpers +import google.auth # type: ignore +from google.auth import credentials as ga_credentials # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.protobuf.json_format import MessageToJson +import google.protobuf.message +import grpc # type: ignore +import proto # type: ignore + +from google.shopping.merchant_products_v1.types import products + +from .base import DEFAULT_CLIENT_INFO, ProductsServiceTransport + +try: + from google.api_core import client_logging # type: ignore + + CLIENT_LOGGING_SUPPORTED = True # pragma: NO COVER +except ImportError: # pragma: NO COVER + CLIENT_LOGGING_SUPPORTED = False + +_LOGGER = std_logging.getLogger(__name__) + + +class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor): # pragma: NO COVER + def intercept_unary_unary(self, continuation, client_call_details, request): + logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + std_logging.DEBUG + ) + if logging_enabled: # pragma: NO COVER + request_metadata = client_call_details.metadata + if isinstance(request, proto.Message): + request_payload = type(request).to_json(request) + elif isinstance(request, google.protobuf.message.Message): + request_payload = MessageToJson(request) + else: + request_payload = f"{type(request).__name__}: {pickle.dumps(request)}" + + request_metadata = { + key: value.decode("utf-8") if isinstance(value, bytes) else value + for key, value in request_metadata + } + grpc_request = { + "payload": request_payload, + "requestMethod": "grpc", + "metadata": dict(request_metadata), + } + _LOGGER.debug( + f"Sending request for {client_call_details.method}", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductsService", + "rpcName": str(client_call_details.method), + "request": grpc_request, + "metadata": grpc_request["metadata"], + }, + ) + response = continuation(client_call_details, request) + if logging_enabled: # pragma: NO COVER + response_metadata = response.trailing_metadata() + # Convert gRPC metadata `` to list of tuples + metadata = ( + dict([(k, str(v)) for k, v in response_metadata]) + if response_metadata + else None + ) + result = response.result() + if isinstance(result, proto.Message): + response_payload = type(result).to_json(result) + elif isinstance(result, google.protobuf.message.Message): + response_payload = MessageToJson(result) + else: + response_payload = f"{type(result).__name__}: {pickle.dumps(result)}" + grpc_response = { + "payload": response_payload, + "metadata": metadata, + "status": "OK", + } + _LOGGER.debug( + f"Received response for {client_call_details.method}.", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductsService", + "rpcName": client_call_details.method, + "response": grpc_response, + "metadata": grpc_response["metadata"], + }, + ) + return response + + +class ProductsServiceGrpcTransport(ProductsServiceTransport): + """gRPC backend transport for ProductsService. + + Service to use Product resource. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends protocol buffers over the wire using gRPC (which is built on + top of HTTP/2); the ``grpcio`` package must be installed. + """ + + _stubs: Dict[str, Callable] + + def __init__( + self, + *, + host: str = "merchantapi.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + channel: Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]] = None, + api_mtls_endpoint: Optional[str] = None, + client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to (default: 'merchantapi.googleapis.com'). + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + This argument is ignored if a ``channel`` instance is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if a ``channel`` instance is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if a ``channel`` instance is provided. + channel (Optional[Union[grpc.Channel, Callable[..., grpc.Channel]]]): + A ``Channel`` instance through which to make calls, or a Callable + that constructs and returns one. If set to None, ``self.create_channel`` + is used to create the channel. If a Callable is given, it will be called + with the same arguments as used in ``self.create_channel``. + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create + a mutual TLS channel with client SSL credentials from + ``client_cert_source`` or application default SSL credentials. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for the grpc channel. It is ignored if a ``channel`` instance is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure a mutual TLS channel. It is + ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport + creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + self._grpc_channel = None + self._ssl_channel_credentials = ssl_channel_credentials + self._stubs: Dict[str, Callable] = {} + + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + + if isinstance(channel, grpc.Channel): + # Ignore credentials if a channel was passed. + credentials = None + self._ignore_credentials = True + # If a channel was explicitly provided, set it. + self._grpc_channel = channel + self._ssl_channel_credentials = None + + else: + if api_mtls_endpoint: + host = api_mtls_endpoint + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + self._ssl_channel_credentials = SslCredentials().ssl_credentials + + else: + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + + # The base transport sets the host, credentials and scopes + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + + if not self._grpc_channel: + # initialize with the provided callable or the default channel + channel_init = channel or type(self).create_channel + self._grpc_channel = channel_init( + self._host, + # use the credentials which are saved + credentials=self._credentials, + # Set ``credentials_file`` to ``None`` here as + # the credentials that we saved earlier should be used. + credentials_file=None, + scopes=self._scopes, + ssl_credentials=self._ssl_channel_credentials, + quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + self._interceptor = _LoggingClientInterceptor() + self._logged_channel = grpc.intercept_channel( + self._grpc_channel, self._interceptor + ) + + # Wrap messages. This must be done after self._logged_channel exists + self._prep_wrapped_messages(client_info) + + @classmethod + def create_channel( + cls, + host: str = "merchantapi.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, + **kwargs, + ) -> grpc.Channel: + """Create and return a gRPC channel object. + Args: + host (Optional[str]): The host for the channel to use. + credentials (Optional[~.Credentials]): The + authorization credentials to attach to requests. These + credentials identify this application to the service. If + none are specified, the client will attempt to ascertain + the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + kwargs (Optional[dict]): Keyword arguments, which are passed to the + channel creation. + Returns: + grpc.Channel: A gRPC channel object. + + Raises: + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + + return grpc_helpers.create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + quota_project_id=quota_project_id, + default_scopes=cls.AUTH_SCOPES, + scopes=scopes, + default_host=cls.DEFAULT_HOST, + **kwargs, + ) + + @property + def grpc_channel(self) -> grpc.Channel: + """Return the channel designed to connect to this service.""" + return self._grpc_channel + + @property + def get_product(self) -> Callable[[products.GetProductRequest], products.Product]: + r"""Return a callable for the get product method over gRPC. + + Retrieves the processed product from your Merchant + Center account. + After inserting, updating, or deleting a product input, + it may take several minutes before the updated final + product can be retrieved. + + Returns: + Callable[[~.GetProductRequest], + ~.Product]: + 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 "get_product" not in self._stubs: + self._stubs["get_product"] = self._logged_channel.unary_unary( + "/google.shopping.merchant.products.v1.ProductsService/GetProduct", + request_serializer=products.GetProductRequest.serialize, + response_deserializer=products.Product.deserialize, + ) + return self._stubs["get_product"] + + @property + def list_products( + self, + ) -> Callable[[products.ListProductsRequest], products.ListProductsResponse]: + r"""Return a callable for the list products method over gRPC. + + Lists the processed products in your Merchant Center account. + The response might contain fewer items than specified by + ``pageSize``. Rely on ``pageToken`` to determine if there are + more items to be requested. + + After inserting, updating, or deleting a product input, it may + take several minutes before the updated processed product can be + retrieved. + + Returns: + Callable[[~.ListProductsRequest], + ~.ListProductsResponse]: + 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 "list_products" not in self._stubs: + self._stubs["list_products"] = self._logged_channel.unary_unary( + "/google.shopping.merchant.products.v1.ProductsService/ListProducts", + request_serializer=products.ListProductsRequest.serialize, + response_deserializer=products.ListProductsResponse.deserialize, + ) + return self._stubs["list_products"] + + def close(self): + self._logged_channel.close() + + @property + def kind(self) -> str: + return "grpc" + + +__all__ = ("ProductsServiceGrpcTransport",) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/grpc_asyncio.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/grpc_asyncio.py new file mode 100644 index 000000000000..e52820712ed5 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/grpc_asyncio.py @@ -0,0 +1,423 @@ +# -*- 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. +# +import inspect +import json +import logging as std_logging +import pickle +from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union +import warnings + +from google.api_core import exceptions as core_exceptions +from google.api_core import gapic_v1, grpc_helpers_async +from google.api_core import retry_async as retries +from google.auth import credentials as ga_credentials # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.protobuf.json_format import MessageToJson +import google.protobuf.message +import grpc # type: ignore +from grpc.experimental import aio # type: ignore +import proto # type: ignore + +from google.shopping.merchant_products_v1.types import products + +from .base import DEFAULT_CLIENT_INFO, ProductsServiceTransport +from .grpc import ProductsServiceGrpcTransport + +try: + from google.api_core import client_logging # type: ignore + + CLIENT_LOGGING_SUPPORTED = True # pragma: NO COVER +except ImportError: # pragma: NO COVER + CLIENT_LOGGING_SUPPORTED = False + +_LOGGER = std_logging.getLogger(__name__) + + +class _LoggingClientAIOInterceptor( + grpc.aio.UnaryUnaryClientInterceptor +): # pragma: NO COVER + async def intercept_unary_unary(self, continuation, client_call_details, request): + logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + std_logging.DEBUG + ) + if logging_enabled: # pragma: NO COVER + request_metadata = client_call_details.metadata + if isinstance(request, proto.Message): + request_payload = type(request).to_json(request) + elif isinstance(request, google.protobuf.message.Message): + request_payload = MessageToJson(request) + else: + request_payload = f"{type(request).__name__}: {pickle.dumps(request)}" + + request_metadata = { + key: value.decode("utf-8") if isinstance(value, bytes) else value + for key, value in request_metadata + } + grpc_request = { + "payload": request_payload, + "requestMethod": "grpc", + "metadata": dict(request_metadata), + } + _LOGGER.debug( + f"Sending request for {client_call_details.method}", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductsService", + "rpcName": str(client_call_details.method), + "request": grpc_request, + "metadata": grpc_request["metadata"], + }, + ) + response = await continuation(client_call_details, request) + if logging_enabled: # pragma: NO COVER + response_metadata = await response.trailing_metadata() + # Convert gRPC metadata `` to list of tuples + metadata = ( + dict([(k, str(v)) for k, v in response_metadata]) + if response_metadata + else None + ) + result = await response + if isinstance(result, proto.Message): + response_payload = type(result).to_json(result) + elif isinstance(result, google.protobuf.message.Message): + response_payload = MessageToJson(result) + else: + response_payload = f"{type(result).__name__}: {pickle.dumps(result)}" + grpc_response = { + "payload": response_payload, + "metadata": metadata, + "status": "OK", + } + _LOGGER.debug( + f"Received response to rpc {client_call_details.method}.", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductsService", + "rpcName": str(client_call_details.method), + "response": grpc_response, + "metadata": grpc_response["metadata"], + }, + ) + return response + + +class ProductsServiceGrpcAsyncIOTransport(ProductsServiceTransport): + """gRPC AsyncIO backend transport for ProductsService. + + Service to use Product resource. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends protocol buffers over the wire using gRPC (which is built on + top of HTTP/2); the ``grpcio`` package must be installed. + """ + + _grpc_channel: aio.Channel + _stubs: Dict[str, Callable] = {} + + @classmethod + def create_channel( + cls, + host: str = "merchantapi.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, + **kwargs, + ) -> aio.Channel: + """Create and return a gRPC AsyncIO channel object. + Args: + host (Optional[str]): The host for the channel to use. + credentials (Optional[~.Credentials]): The + authorization credentials to attach to requests. These + credentials identify this application to the service. If + none are specified, the client will attempt to ascertain + the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + kwargs (Optional[dict]): Keyword arguments, which are passed to the + channel creation. + Returns: + aio.Channel: A gRPC AsyncIO channel object. + """ + + return grpc_helpers_async.create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + quota_project_id=quota_project_id, + default_scopes=cls.AUTH_SCOPES, + scopes=scopes, + default_host=cls.DEFAULT_HOST, + **kwargs, + ) + + def __init__( + self, + *, + host: str = "merchantapi.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None, + api_mtls_endpoint: Optional[str] = None, + client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to (default: 'merchantapi.googleapis.com'). + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + This argument is ignored if a ``channel`` instance is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if a ``channel`` instance is provided. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]): + A ``Channel`` instance through which to make calls, or a Callable + that constructs and returns one. If set to None, ``self.create_channel`` + is used to create the channel. If a Callable is given, it will be called + with the same arguments as used in ``self.create_channel``. + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create + a mutual TLS channel with client SSL credentials from + ``client_cert_source`` or application default SSL credentials. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for the grpc channel. It is ignored if a ``channel`` instance is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure a mutual TLS channel. It is + ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + self._grpc_channel = None + self._ssl_channel_credentials = ssl_channel_credentials + self._stubs: Dict[str, Callable] = {} + + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + + if isinstance(channel, aio.Channel): + # Ignore credentials if a channel was passed. + credentials = None + self._ignore_credentials = True + # If a channel was explicitly provided, set it. + self._grpc_channel = channel + self._ssl_channel_credentials = None + else: + if api_mtls_endpoint: + host = api_mtls_endpoint + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + self._ssl_channel_credentials = SslCredentials().ssl_credentials + + else: + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + + # The base transport sets the host, credentials and scopes + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + + if not self._grpc_channel: + # initialize with the provided callable or the default channel + channel_init = channel or type(self).create_channel + self._grpc_channel = channel_init( + self._host, + # use the credentials which are saved + credentials=self._credentials, + # Set ``credentials_file`` to ``None`` here as + # the credentials that we saved earlier should be used. + credentials_file=None, + scopes=self._scopes, + ssl_credentials=self._ssl_channel_credentials, + quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + self._interceptor = _LoggingClientAIOInterceptor() + self._grpc_channel._unary_unary_interceptors.append(self._interceptor) + self._logged_channel = self._grpc_channel + self._wrap_with_kind = ( + "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters + ) + # Wrap messages. This must be done after self._logged_channel exists + self._prep_wrapped_messages(client_info) + + @property + def grpc_channel(self) -> aio.Channel: + """Create the channel designed to connect to this service. + + This property caches on the instance; repeated calls return + the same channel. + """ + # Return the channel from cache. + return self._grpc_channel + + @property + def get_product( + self, + ) -> Callable[[products.GetProductRequest], Awaitable[products.Product]]: + r"""Return a callable for the get product method over gRPC. + + Retrieves the processed product from your Merchant + Center account. + After inserting, updating, or deleting a product input, + it may take several minutes before the updated final + product can be retrieved. + + Returns: + Callable[[~.GetProductRequest], + Awaitable[~.Product]]: + 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 "get_product" not in self._stubs: + self._stubs["get_product"] = self._logged_channel.unary_unary( + "/google.shopping.merchant.products.v1.ProductsService/GetProduct", + request_serializer=products.GetProductRequest.serialize, + response_deserializer=products.Product.deserialize, + ) + return self._stubs["get_product"] + + @property + def list_products( + self, + ) -> Callable[ + [products.ListProductsRequest], Awaitable[products.ListProductsResponse] + ]: + r"""Return a callable for the list products method over gRPC. + + Lists the processed products in your Merchant Center account. + The response might contain fewer items than specified by + ``pageSize``. Rely on ``pageToken`` to determine if there are + more items to be requested. + + After inserting, updating, or deleting a product input, it may + take several minutes before the updated processed product can be + retrieved. + + Returns: + Callable[[~.ListProductsRequest], + Awaitable[~.ListProductsResponse]]: + 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 "list_products" not in self._stubs: + self._stubs["list_products"] = self._logged_channel.unary_unary( + "/google.shopping.merchant.products.v1.ProductsService/ListProducts", + request_serializer=products.ListProductsRequest.serialize, + response_deserializer=products.ListProductsResponse.deserialize, + ) + return self._stubs["list_products"] + + def _prep_wrapped_messages(self, client_info): + """Precompute the wrapped methods, overriding the base class method to use async wrappers.""" + self._wrapped_methods = { + self.get_product: self._wrap_method( + self.get_product, + default_timeout=None, + client_info=client_info, + ), + self.list_products: self._wrap_method( + self.list_products, + default_timeout=None, + client_info=client_info, + ), + } + + def _wrap_method(self, func, *args, **kwargs): + if self._wrap_with_kind: # pragma: NO COVER + kwargs["kind"] = self.kind + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) + + def close(self): + return self._logged_channel.close() + + @property + def kind(self) -> str: + return "grpc_asyncio" + + +__all__ = ("ProductsServiceGrpcAsyncIOTransport",) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/rest.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/rest.py new file mode 100644 index 000000000000..dde4108aebb1 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/rest.py @@ -0,0 +1,604 @@ +# -*- 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. +# +import dataclasses +import json # type: ignore +import logging +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +from google.api_core import exceptions as core_exceptions +from google.api_core import gapic_v1, rest_helpers, rest_streaming +from google.api_core import retry as retries +from google.auth import credentials as ga_credentials # type: ignore +from google.auth.transport.requests import AuthorizedSession # type: ignore +import google.protobuf +from google.protobuf import json_format +from requests import __version__ as requests_version + +from google.shopping.merchant_products_v1.types import products + +from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO +from .rest_base import _BaseProductsServiceRestTransport + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object, None] # type: ignore + +try: + from google.api_core import client_logging # type: ignore + + CLIENT_LOGGING_SUPPORTED = True # pragma: NO COVER +except ImportError: # pragma: NO COVER + CLIENT_LOGGING_SUPPORTED = False + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=f"requests@{requests_version}", +) + +if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"): # pragma: NO COVER + DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__ + + +class ProductsServiceRestInterceptor: + """Interceptor for ProductsService. + + Interceptors are used to manipulate requests, request metadata, and responses + in arbitrary ways. + Example use cases include: + * Logging + * Verifying requests according to service or custom semantics + * Stripping extraneous information from responses + + These use cases and more can be enabled by injecting an + instance of a custom subclass when constructing the ProductsServiceRestTransport. + + .. code-block:: python + class MyCustomProductsServiceInterceptor(ProductsServiceRestInterceptor): + def pre_get_product(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_product(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_products(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_products(self, response): + logging.log(f"Received response: {response}") + return response + + transport = ProductsServiceRestTransport(interceptor=MyCustomProductsServiceInterceptor()) + client = ProductsServiceClient(transport=transport) + + + """ + + def pre_get_product( + self, + request: products.GetProductRequest, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[products.GetProductRequest, Sequence[Tuple[str, Union[str, bytes]]]]: + """Pre-rpc interceptor for get_product + + Override in a subclass to manipulate the request or metadata + before they are sent to the ProductsService server. + """ + return request, metadata + + def post_get_product(self, response: products.Product) -> products.Product: + """Post-rpc interceptor for get_product + + DEPRECATED. Please use the `post_get_product_with_metadata` + interceptor instead. + + Override in a subclass to read or manipulate the response + after it is returned by the ProductsService server but before + it is returned to user code. This `post_get_product` interceptor runs + before the `post_get_product_with_metadata` interceptor. + """ + return response + + def post_get_product_with_metadata( + self, + response: products.Product, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[products.Product, Sequence[Tuple[str, Union[str, bytes]]]]: + """Post-rpc interceptor for get_product + + Override in a subclass to read or manipulate the response or metadata after it + is returned by the ProductsService server but before it is returned to user code. + + We recommend only using this `post_get_product_with_metadata` + interceptor in new development instead of the `post_get_product` interceptor. + When both interceptors are used, this `post_get_product_with_metadata` interceptor runs after the + `post_get_product` interceptor. The (possibly modified) response returned by + `post_get_product` will be passed to + `post_get_product_with_metadata`. + """ + return response, metadata + + def pre_list_products( + self, + request: products.ListProductsRequest, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[products.ListProductsRequest, Sequence[Tuple[str, Union[str, bytes]]]]: + """Pre-rpc interceptor for list_products + + Override in a subclass to manipulate the request or metadata + before they are sent to the ProductsService server. + """ + return request, metadata + + def post_list_products( + self, response: products.ListProductsResponse + ) -> products.ListProductsResponse: + """Post-rpc interceptor for list_products + + DEPRECATED. Please use the `post_list_products_with_metadata` + interceptor instead. + + Override in a subclass to read or manipulate the response + after it is returned by the ProductsService server but before + it is returned to user code. This `post_list_products` interceptor runs + before the `post_list_products_with_metadata` interceptor. + """ + return response + + def post_list_products_with_metadata( + self, + response: products.ListProductsResponse, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[products.ListProductsResponse, Sequence[Tuple[str, Union[str, bytes]]]]: + """Post-rpc interceptor for list_products + + Override in a subclass to read or manipulate the response or metadata after it + is returned by the ProductsService server but before it is returned to user code. + + We recommend only using this `post_list_products_with_metadata` + interceptor in new development instead of the `post_list_products` interceptor. + When both interceptors are used, this `post_list_products_with_metadata` interceptor runs after the + `post_list_products` interceptor. The (possibly modified) response returned by + `post_list_products` will be passed to + `post_list_products_with_metadata`. + """ + return response, metadata + + +@dataclasses.dataclass +class ProductsServiceRestStub: + _session: AuthorizedSession + _host: str + _interceptor: ProductsServiceRestInterceptor + + +class ProductsServiceRestTransport(_BaseProductsServiceRestTransport): + """REST backend synchronous transport for ProductsService. + + Service to use Product resource. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + """ + + def __init__( + self, + *, + host: str = "merchantapi.googleapis.com", + credentials: Optional[ga_credentials.Credentials] = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + interceptor: Optional[ProductsServiceRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to (default: 'merchantapi.googleapis.com'). + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` is provided. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + url_scheme=url_scheme, + api_audience=api_audience, + ) + self._session = AuthorizedSession( + self._credentials, default_host=self.DEFAULT_HOST + ) + if client_cert_source_for_mtls: + self._session.configure_mtls_channel(client_cert_source_for_mtls) + self._interceptor = interceptor or ProductsServiceRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _GetProduct( + _BaseProductsServiceRestTransport._BaseGetProduct, ProductsServiceRestStub + ): + def __hash__(self): + return hash("ProductsServiceRestTransport.GetProduct") + + @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), + ) + return response + + def __call__( + self, + request: products.GetProductRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> products.Product: + r"""Call the get product method over HTTP. + + Args: + request (~.products.GetProductRequest): + The request object. Request message for the GetProduct + method. + 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: + ~.products.Product: + The processed product, built from multiple [product + inputs][google.shopping.merchant.products.v1main.ProductInput] + after applying rules and supplemental data sources. This + processed product matches what is shown in your Merchant + Center account. Each product is built from exactly one + primary data source product input, and multiple + supplemental data source inputs. After inserting, + updating, or deleting a product input, it may take + several minutes before the updated processed product can + be retrieved. + + All fields in the processed product and its sub-messages + match the name of their corresponding attribute in the + `Product data + specification `__ + with some exceptions. + + """ + + http_options = ( + _BaseProductsServiceRestTransport._BaseGetProduct._get_http_options() + ) + + request, metadata = self._interceptor.pre_get_product(request, metadata) + transcoded_request = _BaseProductsServiceRestTransport._BaseGetProduct._get_transcoded_request( + http_options, request + ) + + # Jsonify the query params + query_params = _BaseProductsServiceRestTransport._BaseGetProduct._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.shopping.merchant.products_v1.ProductsServiceClient.GetProduct", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductsService", + "rpcName": "GetProduct", + "httpRequest": http_request, + "metadata": http_request["headers"], + }, + ) + + # Send the request + response = ProductsServiceRestTransport._GetProduct._get_response( + self._host, + metadata, + query_params, + self._session, + timeout, + transcoded_request, + ) + + # 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 = products.Product() + pb_resp = products.Product.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + + resp = self._interceptor.post_get_product(resp) + response_metadata = [(k, str(v)) for k, v in response.headers.items()] + resp, _ = self._interceptor.post_get_product_with_metadata( + resp, response_metadata + ) + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + try: + response_payload = products.Product.to_json(response) + except: + response_payload = None + http_response = { + "payload": response_payload, + "headers": dict(response.headers), + "status": response.status_code, + } + _LOGGER.debug( + "Received response for google.shopping.merchant.products_v1.ProductsServiceClient.get_product", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductsService", + "rpcName": "GetProduct", + "metadata": http_response["headers"], + "httpResponse": http_response, + }, + ) + return resp + + class _ListProducts( + _BaseProductsServiceRestTransport._BaseListProducts, ProductsServiceRestStub + ): + def __hash__(self): + return hash("ProductsServiceRestTransport.ListProducts") + + @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), + ) + return response + + def __call__( + self, + request: products.ListProductsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> products.ListProductsResponse: + r"""Call the list products method over HTTP. + + Args: + request (~.products.ListProductsRequest): + The request object. Request message for the ListProducts + method. + 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: + ~.products.ListProductsResponse: + Response message for the ListProducts + method. + + """ + + http_options = ( + _BaseProductsServiceRestTransport._BaseListProducts._get_http_options() + ) + + request, metadata = self._interceptor.pre_list_products(request, metadata) + transcoded_request = _BaseProductsServiceRestTransport._BaseListProducts._get_transcoded_request( + http_options, request + ) + + # Jsonify the query params + query_params = _BaseProductsServiceRestTransport._BaseListProducts._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.shopping.merchant.products_v1.ProductsServiceClient.ListProducts", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductsService", + "rpcName": "ListProducts", + "httpRequest": http_request, + "metadata": http_request["headers"], + }, + ) + + # Send the request + response = ProductsServiceRestTransport._ListProducts._get_response( + self._host, + metadata, + query_params, + self._session, + timeout, + transcoded_request, + ) + + # 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 = products.ListProductsResponse() + pb_resp = products.ListProductsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + + resp = self._interceptor.post_list_products(resp) + response_metadata = [(k, str(v)) for k, v in response.headers.items()] + resp, _ = self._interceptor.post_list_products_with_metadata( + resp, response_metadata + ) + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + try: + response_payload = products.ListProductsResponse.to_json(response) + except: + response_payload = None + http_response = { + "payload": response_payload, + "headers": dict(response.headers), + "status": response.status_code, + } + _LOGGER.debug( + "Received response for google.shopping.merchant.products_v1.ProductsServiceClient.list_products", + extra={ + "serviceName": "google.shopping.merchant.products.v1.ProductsService", + "rpcName": "ListProducts", + "metadata": http_response["headers"], + "httpResponse": http_response, + }, + ) + return resp + + @property + def get_product(self) -> Callable[[products.GetProductRequest], products.Product]: + # 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._GetProduct(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_products( + self, + ) -> Callable[[products.ListProductsRequest], products.ListProductsResponse]: + # 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._ListProducts(self._session, self._host, self._interceptor) # type: ignore + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("ProductsServiceRestTransport",) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/rest_base.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/rest_base.py new file mode 100644 index 000000000000..f3ed6a6ffcf6 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/services/products_service/transports/rest_base.py @@ -0,0 +1,185 @@ +# -*- 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. +# +import json # type: ignore +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union + +from google.api_core import gapic_v1, path_template +from google.protobuf import json_format + +from google.shopping.merchant_products_v1.types import products + +from .base import DEFAULT_CLIENT_INFO, ProductsServiceTransport + + +class _BaseProductsServiceRestTransport(ProductsServiceTransport): + """Base REST backend transport for ProductsService. + + Note: This class is not meant to be used directly. Use its sync and + async sub-classes instead. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + """ + + def __init__( + self, + *, + host: str = "merchantapi.googleapis.com", + credentials: Optional[Any] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + always_use_jwt_access: Optional[bool] = False, + url_scheme: str = "https", + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + Args: + host (Optional[str]): + The hostname to connect to (default: 'merchantapi.googleapis.com'). + credentials (Optional[Any]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + maybe_url_match = re.match("^(?Phttp(?:s)?://)?(?P.*)$", host) + if maybe_url_match is None: + raise ValueError( + f"Unexpected hostname structure: {host}" + ) # pragma: NO COVER + + url_match_items = maybe_url_match.groupdict() + + host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host + + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=always_use_jwt_access, + api_audience=api_audience, + ) + + class _BaseGetProduct: + 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": "get", + "uri": "/products/v1/{name=accounts/*/products/*}", + }, + ] + return http_options + + @staticmethod + def _get_transcoded_request(http_options, request): + pb_request = products.GetProductRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + return transcoded_request + + @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( + _BaseProductsServiceRestTransport._BaseGetProduct._get_unset_required_fields( + query_params + ) + ) + + query_params["$alt"] = "json;enum-encoding=int" + return query_params + + class _BaseListProducts: + 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": "get", + "uri": "/products/v1/{parent=accounts/*}/products", + }, + ] + return http_options + + @staticmethod + def _get_transcoded_request(http_options, request): + pb_request = products.ListProductsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + return transcoded_request + + @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( + _BaseProductsServiceRestTransport._BaseListProducts._get_unset_required_fields( + query_params + ) + ) + + query_params["$alt"] = "json;enum-encoding=int" + return query_params + + +__all__ = ("_BaseProductsServiceRestTransport",) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/types/__init__.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/types/__init__.py new file mode 100644 index 000000000000..0c8a62be54d8 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/types/__init__.py @@ -0,0 +1,112 @@ +# -*- 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 .productinputs import ( + DeleteProductInputRequest, + InsertProductInputRequest, + ProductInput, + UpdateProductInputRequest, +) +from .products import ( + GetProductRequest, + ListProductsRequest, + ListProductsResponse, + Product, +) +from .products_common import ( + AgeGroup, + AutomatedDiscounts, + Availability, + CertificationAuthority, + CertificationName, + CloudExportAdditionalProperties, + Condition, + CreditType, + DigitalSourceType, + EnergyEfficiencyClass, + FreeShippingThreshold, + Gender, + LoyaltyPoints, + LoyaltyProgram, + Pause, + PickupMethod, + PickupSla, + ProductAttributes, + ProductCertification, + ProductDetail, + ProductDimension, + ProductInstallment, + ProductStatus, + ProductSustainabilityIncentive, + ProductWeight, + Shipping, + ShippingDimension, + ShippingWeight, + SizeSystem, + SizeType, + StructuredDescription, + StructuredTitle, + SubscriptionCost, + SubscriptionPeriod, + UnitPricingBaseMeasure, + UnitPricingMeasure, +) + +__all__ = ( + "DeleteProductInputRequest", + "InsertProductInputRequest", + "ProductInput", + "UpdateProductInputRequest", + "GetProductRequest", + "ListProductsRequest", + "ListProductsResponse", + "Product", + "AutomatedDiscounts", + "CloudExportAdditionalProperties", + "FreeShippingThreshold", + "LoyaltyPoints", + "LoyaltyProgram", + "ProductAttributes", + "ProductCertification", + "ProductDetail", + "ProductDimension", + "ProductInstallment", + "ProductStatus", + "ProductSustainabilityIncentive", + "ProductWeight", + "Shipping", + "ShippingDimension", + "ShippingWeight", + "StructuredDescription", + "StructuredTitle", + "SubscriptionCost", + "UnitPricingBaseMeasure", + "UnitPricingMeasure", + "AgeGroup", + "Availability", + "CertificationAuthority", + "CertificationName", + "Condition", + "CreditType", + "DigitalSourceType", + "EnergyEfficiencyClass", + "Gender", + "Pause", + "PickupMethod", + "PickupSla", + "SizeSystem", + "SizeType", + "SubscriptionPeriod", +) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/types/productinputs.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/types/productinputs.py new file mode 100644 index 000000000000..5f747bb6bb44 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/types/productinputs.py @@ -0,0 +1,312 @@ +# -*- 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 + +from google.protobuf import field_mask_pb2 # type: ignore +from google.shopping.type.types import types +import proto # type: ignore + +from google.shopping.merchant_products_v1.types import products_common + +__protobuf__ = proto.module( + package="google.shopping.merchant.products.v1", + manifest={ + "ProductInput", + "InsertProductInputRequest", + "UpdateProductInputRequest", + "DeleteProductInputRequest", + }, +) + + +class ProductInput(proto.Message): + r"""This resource represents input data you submit for a product, not + the processed product that you see in Merchant Center, in Shopping + ads, or across Google surfaces. Product inputs, rules and + supplemental data source data are combined to create the processed + [Product][google.shopping.merchant.products.v1.Product]. For more + information, see `Manage + products `__. + + Required product input attributes to pass data validation checks are + primarily defined in the `Products Data + Specification `__. + + The following attributes are required: + [feedLabel][google.shopping.merchant.products.v1.Product.feed_label], + [contentLanguage][google.shopping.merchant.products.v1.Product.content_language] + and + [offerId][google.shopping.merchant.products.v1.Product.offer_id]. + + After inserting, updating, or deleting a product input, it may take + several minutes before the processed product can be retrieved. + + All fields in the product input and its sub-messages match the + English name of their corresponding attribute in the `Products Data + Specification `__ + with `some + exceptions `__. + The following reference documentation lists the field names in the + **camelCase** casing style while the Products Data Specification + lists the names in the **snake_case** casing style. + + + .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields + + Attributes: + name (str): + Identifier. The name of the product input. Format: + ``accounts/{account}/productInputs/{productinput}`` where + the last section ``productinput`` consists of: + ``content_language~feed_label~offer_id`` example for product + input name is ``accounts/123/productInputs/en~US~sku123``. A + legacy local product input name would be + ``accounts/123/productInputs/local~en~US~sku123``. Note: For + calls to the v1beta version, the ``productInput`` section + consists of: + ``channel~content_language~feed_label~offer_id``, for + example: ``accounts/123/productInputs/online~en~US~sku123``. + product (str): + Output only. The name of the processed product. Format: + ``accounts/{account}/products/{product}`` + legacy_local (bool): + Immutable. Determines whether the product is **only** + targeting local destinations and whether the product name + should be distinguished with a ``local~`` prefix. For + example, ``accounts/123/productInputs/local~en~US~sku123``. + If a product that is not ``legacy_local`` is already + targeting local destinations, creating a ``legacy_local`` + product with an otherwise matching name will fail. + offer_id (str): + Required. Immutable. Your unique identifier for the product. + This is the same for the product input and processed + product. Leading and trailing whitespaces are stripped and + multiple whitespaces are replaced by a single whitespace + upon submission. See the `products data + specification `__ + for details. + content_language (str): + Required. Immutable. The two-letter `ISO + 639-1 `__ language + code for the product. + feed_label (str): + Required. Immutable. The feed label that lets you categorize + and identify your products. The maximum allowed characters + are 20, and the supported characters are ``A-Z``, ``0-9``, + hyphen, and underscore. The feed label must not include any + spaces. For more information, see `Using feed + labels `__. + version_number (int): + Optional. Immutable. Represents the existing version + (freshness) of the product, which can be used to preserve + the right order when multiple updates are done at the same + time. + + If set, the insertion is prevented when version number is + lower than the current version number of the existing + product. Re-insertion (for example, product refresh after 30 + days) can be performed with the current ``version_number``. + + Only supported for insertions into primary data sources. Do + not set this field for updates. Do not set this field for + insertions into supplemental data sources. + + If the operation is prevented, the aborted exception will be + thrown. + + This field is a member of `oneof`_ ``_version_number``. + product_attributes (google.shopping.merchant_products_v1.types.ProductAttributes): + Optional. A list of strongly-typed product + attributes. + custom_attributes (MutableSequence[google.shopping.type.types.CustomAttribute]): + Optional. A list of custom (merchant-provided) attributes. + It can also be used for submitting any attribute of the data + specification in its generic form (for example, + ``{ "name": "size type", "value": "regular" }``). This is + useful for submitting attributes not explicitly exposed by + the API. Maximum allowed number of characters for each + custom attribute is 10240 (represents sum of characters for + name and value). Maximum 2500 custom attributes can be set + per product, with total size of 102.4kB. Underscores in + custom attribute names are replaced by spaces upon + insertion. + """ + + name: str = proto.Field( + proto.STRING, + number=1, + ) + product: str = proto.Field( + proto.STRING, + number=2, + ) + legacy_local: bool = proto.Field( + proto.BOOL, + number=10, + ) + offer_id: str = proto.Field( + proto.STRING, + number=4, + ) + content_language: str = proto.Field( + proto.STRING, + number=5, + ) + feed_label: str = proto.Field( + proto.STRING, + number=6, + ) + version_number: int = proto.Field( + proto.INT64, + number=7, + optional=True, + ) + product_attributes: products_common.ProductAttributes = proto.Field( + proto.MESSAGE, + number=11, + message=products_common.ProductAttributes, + ) + custom_attributes: MutableSequence[types.CustomAttribute] = proto.RepeatedField( + proto.MESSAGE, + number=9, + message=types.CustomAttribute, + ) + + +class InsertProductInputRequest(proto.Message): + r"""Request message for the InsertProductInput method. + + Attributes: + parent (str): + Required. The account where this product will be inserted. + Format: ``accounts/{account}`` + product_input (google.shopping.merchant_products_v1.types.ProductInput): + Required. The product input to insert. + data_source (str): + Required. The primary or supplemental product data source + name. If the product already exists and data source provided + is different, then the product will be moved to a new data + source. For more information, see `Overview of Data sources + sub-API `__. + + Only API data sources are supported. + + Format: ``accounts/{account}/dataSources/{datasource}``. For + example, ``accounts/123456/dataSources/104628``. + """ + + parent: str = proto.Field( + proto.STRING, + number=1, + ) + product_input: "ProductInput" = proto.Field( + proto.MESSAGE, + number=2, + message="ProductInput", + ) + data_source: str = proto.Field( + proto.STRING, + number=3, + ) + + +class UpdateProductInputRequest(proto.Message): + r"""Request message for the UpdateProductInput method. + The product (primary input) must exist for the update to + succeed. If the update is for a primary product input, the + existing primary product input must be from the same data + source. + + Attributes: + product_input (google.shopping.merchant_products_v1.types.ProductInput): + Required. The product input resource to + update. Information you submit will be applied + to the processed product as well. + update_mask (google.protobuf.field_mask_pb2.FieldMask): + Optional. The list of product attributes to be updated. + + If the update mask is omitted, then it is treated as implied + field mask equivalent to all fields that are populated (have + a non-empty value). + + Attributes specified in the update mask without a value + specified in the body will be deleted from the product. + + Update mask can only be specified for top level fields in + attributes and custom attributes. + + To specify the update mask for custom attributes you need to + add the ``custom_attribute.`` prefix. + + Providing special "*" value for full product replacement is + not supported. + data_source (str): + Required. The primary or supplemental product data source + where ``data_source`` name identifies the product input to + be updated. + + Only API data sources are supported. + + Format: ``accounts/{account}/dataSources/{datasource}``. For + example, ``accounts/123456/dataSources/104628``. + """ + + product_input: "ProductInput" = proto.Field( + proto.MESSAGE, + number=1, + message="ProductInput", + ) + update_mask: field_mask_pb2.FieldMask = proto.Field( + proto.MESSAGE, + number=2, + message=field_mask_pb2.FieldMask, + ) + data_source: str = proto.Field( + proto.STRING, + number=3, + ) + + +class DeleteProductInputRequest(proto.Message): + r"""Request message for the DeleteProductInput method. + + Attributes: + name (str): + Required. The name of the product input resource to delete. + Format: ``accounts/{account}/productInputs/{product}`` where + the last section ``product`` consists of: + ``content_language~feed_label~offer_id`` example for product + name is ``accounts/123/productInputs/en~US~sku123``. + data_source (str): + Required. The primary or supplemental data source from which + the product input should be deleted. Format: + ``accounts/{account}/dataSources/{datasource}``. For + example, ``accounts/123456/dataSources/104628``. + """ + + name: str = proto.Field( + proto.STRING, + number=1, + ) + data_source: str = proto.Field( + proto.STRING, + number=2, + ) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/types/products.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/types/products.py new file mode 100644 index 000000000000..4962a9d7e043 --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/types/products.py @@ -0,0 +1,271 @@ +# -*- 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 + +from google.shopping.type.types import types +import proto # type: ignore + +from google.shopping.merchant_products_v1.types import products_common + +__protobuf__ = proto.module( + package="google.shopping.merchant.products.v1", + manifest={ + "Product", + "GetProductRequest", + "ListProductsRequest", + "ListProductsResponse", + }, +) + + +class Product(proto.Message): + r"""The processed product, built from multiple [product + inputs][google.shopping.merchant.products.v1main.ProductInput] after + applying rules and supplemental data sources. This processed product + matches what is shown in your Merchant Center account. Each product + is built from exactly one primary data source product input, and + multiple supplemental data source inputs. After inserting, updating, + or deleting a product input, it may take several minutes before the + updated processed product can be retrieved. + + All fields in the processed product and its sub-messages match the + name of their corresponding attribute in the `Product data + specification `__ + with some exceptions. + + + .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields + + Attributes: + name (str): + The name of the product. Format: + ``accounts/{account}/products/{product}`` where the last + section ``product`` consists of: + ``content_language~feed_label~offer_id`` example for product + name is ``accounts/123/products/en~US~sku123``. A legacy + local product name would be + ``accounts/123/products/local~en~US~sku123``. Note: For + calls to the v1beta version, the ``product`` section + consists of: + ``channel~content_language~feed_label~offer_id``, for + example: ``accounts/123/products/online~en~US~sku123``. + legacy_local (bool): + Output only. Determines whether the product is **only** + targeting local destinations and whether the product name + should be distinguished with a ``local~`` prefix. For + example, ``accounts/123/products/local~en~US~sku123``. + offer_id (str): + Output only. Your unique identifier for the product. This is + the same for the product input and processed product. + Leading and trailing whitespaces are stripped and multiple + whitespaces are replaced by a single whitespace upon + submission. See the `product data + specification `__ + for details. + content_language (str): + Output only. The two-letter `ISO + 639-1 `__ language + code for the product. + feed_label (str): + Output only. The feed label lets you categorize and identify + your products. The maximum allowed characters is 20 and the + supported characters are\ ``A-Z``, ``0-9``, hyphen and + underscore. The feed label must not include any spaces. For + more information, see `Using feed + labels `__ + data_source (str): + Output only. The primary data source of the + product. + version_number (int): + Output only. Represents the existing version (freshness) of + the product, which can be used to preserve the right order + when multiple updates are done at the same time. + + If set, the insertion is prevented when version number is + lower than the current version number of the existing + product. Re-insertion (for example, product refresh after 30 + days) can be performed with the current ``version_number``. + + Only supported for insertions into primary data sources. + + If the operation is prevented, the aborted exception will be + thrown. + + This field is a member of `oneof`_ ``_version_number``. + product_attributes (google.shopping.merchant_products_v1.types.ProductAttributes): + Output only. A list of strongly-typed product + attributes. + custom_attributes (MutableSequence[google.shopping.type.types.CustomAttribute]): + Output only. A list of custom (merchant-provided) + attributes. It can also be used to submit any attribute of + the data specification in its generic form (for example, + ``{ "name": "size type", "value": "regular" }``). This is + useful for submitting attributes not explicitly exposed by + the API, such as additional attributes used for Buy on + Google. + product_status (google.shopping.merchant_products_v1.types.ProductStatus): + Output only. The status of a product, data + validation issues, that is, information about a + product computed asynchronously. + automated_discounts (google.shopping.merchant_products_v1.types.AutomatedDiscounts): + Output only. The automated discounts + information for the product. + """ + + name: str = proto.Field( + proto.STRING, + number=1, + ) + legacy_local: bool = proto.Field( + proto.BOOL, + number=11, + ) + offer_id: str = proto.Field( + proto.STRING, + number=3, + ) + content_language: str = proto.Field( + proto.STRING, + number=4, + ) + feed_label: str = proto.Field( + proto.STRING, + number=5, + ) + data_source: str = proto.Field( + proto.STRING, + number=6, + ) + version_number: int = proto.Field( + proto.INT64, + number=7, + optional=True, + ) + product_attributes: products_common.ProductAttributes = proto.Field( + proto.MESSAGE, + number=13, + message=products_common.ProductAttributes, + ) + custom_attributes: MutableSequence[types.CustomAttribute] = proto.RepeatedField( + proto.MESSAGE, + number=9, + message=types.CustomAttribute, + ) + product_status: products_common.ProductStatus = proto.Field( + proto.MESSAGE, + number=10, + message=products_common.ProductStatus, + ) + automated_discounts: products_common.AutomatedDiscounts = proto.Field( + proto.MESSAGE, + number=12, + message=products_common.AutomatedDiscounts, + ) + + +class GetProductRequest(proto.Message): + r"""Request message for the GetProduct method. + + Attributes: + name (str): + Required. The name of the product to retrieve. Format: + ``accounts/{account}/products/{product}`` where the last + section ``product`` consists of: + ``content_language~feed_label~offer_id`` example for product + name is ``accounts/123/products/en~US~sku123``. A legacy + local product name would be + ``accounts/123/products/local~en~US~sku123``. Note: For + calls to the v1beta version, the ``product`` section + consists of: + ``channel~content_language~feed_label~offer_id``, for + example: ``accounts/123/products/online~en~US~sku123``. + """ + + name: str = proto.Field( + proto.STRING, + number=1, + ) + + +class ListProductsRequest(proto.Message): + r"""Request message for the ListProducts method. + + Attributes: + parent (str): + Required. The account to list processed products for. + Format: ``accounts/{account}`` + page_size (int): + The maximum number of products to return. The + service may return fewer than this value. + The maximum value is 1000; values above 1000 + will be coerced to 1000. If unspecified, the + default page size of 25 products will be + returned. + page_token (str): + A page token, received from a previous ``ListProducts`` + call. Provide this to retrieve the subsequent page. + + When paginating, all other parameters provided to + ``ListProducts`` must match the call that provided the page + token. + """ + + parent: str = proto.Field( + proto.STRING, + number=1, + ) + page_size: int = proto.Field( + proto.INT32, + number=2, + ) + page_token: str = proto.Field( + proto.STRING, + number=3, + ) + + +class ListProductsResponse(proto.Message): + r"""Response message for the ListProducts method. + + Attributes: + products (MutableSequence[google.shopping.merchant_products_v1.types.Product]): + The processed products from the specified + account. These are your processed products after + applying rules and supplemental data sources. + next_page_token (str): + A token, which can be sent as ``page_token`` to retrieve the + next page. If this field is omitted, there are no subsequent + pages. + """ + + @property + def raw_page(self): + return self + + products: MutableSequence["Product"] = proto.RepeatedField( + proto.MESSAGE, + number=1, + message="Product", + ) + next_page_token: str = proto.Field( + proto.STRING, + number=2, + ) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/types/products_common.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/types/products_common.py new file mode 100644 index 000000000000..664b1edcb1ef --- /dev/null +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1/types/products_common.py @@ -0,0 +1,2459 @@ +# -*- 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 + +from google.protobuf import timestamp_pb2 # type: ignore +from google.shopping.type.types import types +from google.type import interval_pb2 # type: ignore +import proto # type: ignore + +__protobuf__ = proto.module( + package="google.shopping.merchant.products.v1", + manifest={ + "SubscriptionPeriod", + "AgeGroup", + "Availability", + "Condition", + "Gender", + "CreditType", + "SizeSystem", + "SizeType", + "EnergyEfficiencyClass", + "PickupMethod", + "PickupSla", + "Pause", + "CertificationAuthority", + "CertificationName", + "DigitalSourceType", + "ProductAttributes", + "ShippingWeight", + "ShippingDimension", + "UnitPricingBaseMeasure", + "UnitPricingMeasure", + "SubscriptionCost", + "ProductInstallment", + "LoyaltyPoints", + "LoyaltyProgram", + "Shipping", + "FreeShippingThreshold", + "ProductDetail", + "ProductCertification", + "StructuredTitle", + "StructuredDescription", + "ProductDimension", + "ProductWeight", + "ProductStatus", + "CloudExportAdditionalProperties", + "ProductSustainabilityIncentive", + "AutomatedDiscounts", + }, +) + + +class SubscriptionPeriod(proto.Enum): + r"""The subscription period of the product. + + Values: + SUBSCRIPTION_PERIOD_UNSPECIFIED (0): + Indicates that the subscription period is + unspecified. + MONTH (1): + Indicates that the subscription period is + month. + YEAR (2): + Indicates that the subscription period is + year. + """ + SUBSCRIPTION_PERIOD_UNSPECIFIED = 0 + MONTH = 1 + YEAR = 2 + + +class AgeGroup(proto.Enum): + r"""Target `age + group `__ of + the item. + + Values: + AGE_GROUP_UNSPECIFIED (0): + Age group is not specified. + ADULT (1): + Teens or older. + KIDS (2): + 5-13 years old. + TODDLER (3): + 1-5 years old. + INFANT (4): + 3-12 months old. + NEWBORN (5): + 0-3 months old. + """ + AGE_GROUP_UNSPECIFIED = 0 + ADULT = 1 + KIDS = 2 + TODDLER = 3 + INFANT = 4 + NEWBORN = 5 + + +class Availability(proto.Enum): + r"""`Availability `__ + status of the item. + + Values: + AVAILABILITY_UNSPECIFIED (0): + Availability is not specified. + IN_STOCK (1): + In stock. + OUT_OF_STOCK (2): + Out of stock. + PREORDER (3): + Pre-order. + LIMITED_AVAILABILITY (4): + Limited availability. + BACKORDER (5): + Backorder. + """ + AVAILABILITY_UNSPECIFIED = 0 + IN_STOCK = 1 + OUT_OF_STOCK = 2 + PREORDER = 3 + LIMITED_AVAILABILITY = 4 + BACKORDER = 5 + + +class Condition(proto.Enum): + r"""`Condition `__ + or state of the item. + + Values: + CONDITION_UNSPECIFIED (0): + Default value. This value is unused. + NEW (1): + Brand new, original, unopened packaging. + USED (2): + Previously used, original packaging opened or + missing. + REFURBISHED (3): + Professionally restored to working order, + comes with a warranty, may or may not have the + original packaging. + """ + CONDITION_UNSPECIFIED = 0 + NEW = 1 + USED = 2 + REFURBISHED = 3 + + +class Gender(proto.Enum): + r"""Target + `gender `__ of + the item. + + Values: + GENDER_UNSPECIFIED (0): + Gender is not specified. + MALE (1): + Male. + FEMALE (2): + Female. + UNISEX (3): + Unisex. + """ + GENDER_UNSPECIFIED = 0 + MALE = 1 + FEMALE = 2 + UNISEX = 3 + + +class CreditType(proto.Enum): + r"""Type of installment payments. + + Values: + CREDIT_TYPE_UNSPECIFIED (0): + Default value. This value is unused. + FINANCE (1): + Finance. + LEASE (2): + Lease. + """ + CREDIT_TYPE_UNSPECIFIED = 0 + FINANCE = 1 + LEASE = 2 + + +class SizeSystem(proto.Enum): + r"""System in which the size is specified. Recommended for apparel + items. For more information, see `Size + system `__. + + Values: + SIZE_SYSTEM_UNSPECIFIED (0): + Unspecified size system. + AU (1): + AU. + BR (2): + BR. + CN (3): + CN. + DE (4): + DE. + EU (5): + EU. + FR (6): + FR. + IT (7): + IT. + JP (8): + JP. + MEX (9): + MEX. + UK (10): + UK. + US (11): + US. + """ + SIZE_SYSTEM_UNSPECIFIED = 0 + AU = 1 + BR = 2 + CN = 3 + DE = 4 + EU = 5 + FR = 6 + IT = 7 + JP = 8 + MEX = 9 + UK = 10 + US = 11 + + +class SizeType(proto.Enum): + r"""The cut of the item. It can be used to represent combined size types + for apparel items. Maximum two of size types can be provided, see + `Size type `__. + + Values: + SIZE_TYPE_UNSPECIFIED (0): + The size type is not specified. + REGULAR (1): + Regular size. + PETITE (2): + Petite size. + MATERNITY (3): + Maternity size. + BIG (4): + Big size. + TALL (5): + Tall size. + PLUS (6): + Plus size. + """ + SIZE_TYPE_UNSPECIFIED = 0 + REGULAR = 1 + PETITE = 2 + MATERNITY = 3 + BIG = 4 + TALL = 5 + PLUS = 6 + + +class EnergyEfficiencyClass(proto.Enum): + r"""The `energy efficiency + class `__ as + defined in EU directive 2010/30/EU. + + Values: + ENERGY_EFFICIENCY_CLASS_UNSPECIFIED (0): + The energy efficiency class is unspecified. + APPP (1): + A+++. + APP (2): + A++. + AP (3): + A+. + A (4): + A. + B (5): + B. + C (6): + C. + D (7): + D. + E (8): + E. + F (9): + F. + G (10): + G. + """ + ENERGY_EFFICIENCY_CLASS_UNSPECIFIED = 0 + APPP = 1 + APP = 2 + AP = 3 + A = 4 + B = 5 + C = 6 + D = 7 + E = 8 + F = 9 + G = 10 + + +class PickupMethod(proto.Enum): + r"""The + `pickup `__ + option for the item. + + Values: + PICKUP_METHOD_UNSPECIFIED (0): + Pickup method is not specified. + NOT_SUPPORTED (1): + The item is not available for store pickup. + BUY (2): + The entire transaction occurs online. + RESERVE (3): + The item is reserved online and the + transaction occurs in-store. + SHIP_TO_STORE (4): + The item is purchased online and shipped to a + local store for the customer to pick up. + """ + PICKUP_METHOD_UNSPECIFIED = 0 + NOT_SUPPORTED = 1 + BUY = 2 + RESERVE = 3 + SHIP_TO_STORE = 4 + + +class PickupSla(proto.Enum): + r"""Item store pickup timeline. For more information, see `Pickup + SLA `__. + + Values: + PICKUP_SLA_UNSPECIFIED (0): + Pickup SLA is not specified. + SAME_DAY (1): + Indicates that the product is available for + pickup the same day that the order is placed, + subject to cut off times. + NEXT_DAY (2): + Indicates that the product is available for + pickup the following day that the order is + placed. + TWO_DAY (3): + Indicates that the product will be shipped to + a store for a customer to pick up in 2 days. + THREE_DAY (4): + Indicates that the product will be shipped to + a store for a customer to pick up in 3 days. + FOUR_DAY (5): + Indicates that the product will be shipped to + a store for a customer to pick up in 4 days + FIVE_DAY (6): + Indicates that the product will be shipped to + a store for a customer to pick up in 5 days. + SIX_DAY (7): + Indicates that the product will be shipped to + a store for a customer to pick up in 6 days. + MULTI_WEEK (8): + Indicates that the product will be shipped to + a store for a customer to pick up in one week or + more. + """ + PICKUP_SLA_UNSPECIFIED = 0 + SAME_DAY = 1 + NEXT_DAY = 2 + TWO_DAY = 3 + THREE_DAY = 4 + FOUR_DAY = 5 + FIVE_DAY = 6 + SIX_DAY = 7 + MULTI_WEEK = 8 + + +class Pause(proto.Enum): + r"""Publication of this item will be temporarily + `paused `__. + + Values: + PAUSE_UNSPECIFIED (0): + The pause is unspecified. + ADS (1): + You’re currently pausing your product for all + ads locations (including Shopping Ads, Display + Ads, and local inventory ads). + ALL (2): + You’re currently pausing your product for all + Shopping locations (including Shopping Ads, + Display Ads, local inventory ads, Buy on Google, + and free listings). + """ + PAUSE_UNSPECIFIED = 0 + ADS = 1 + ALL = 2 + + +class CertificationAuthority(proto.Enum): + r"""The certification authority. + + Values: + CERTIFICATION_AUTHORITY_UNSPECIFIED (0): + Certification authority is not specified. + ADEME (1): + For the French CO2 emissions class for + vehicles. + BMWK (2): + For the German CO2 emissions classes for + vehicles. + EPA (3): + Environment Protection Agency. + EC (4): + European Commission for energy labels in the + EU. + """ + CERTIFICATION_AUTHORITY_UNSPECIFIED = 0 + ADEME = 1 + BMWK = 2 + EPA = 3 + EC = 4 + + +class CertificationName(proto.Enum): + r"""The name of the certification. + + Values: + CERTIFICATION_NAME_UNSPECIFIED (0): + Certification name is not specified. + ENERGY_STAR (1): + Energy Star. + ENERGY_STAR_MOST_EFFICIENT (2): + Energy Star Most Efficient. + EPREL (3): + Represents energy efficiency certifications + in the EU European Registry for Energy Labeling + (EPREL) database. + EU_ECOLABEL (4): + EU Ecolabel. + VEHICLE_ENERGY_EFFICIENCY (5): + The overall CO2 class of a vehicle + VEHICLE_ENERGY_EFFICIENCY_DISCHARGED_BATTERY (6): + For the CO2 class of a vehicle with a + discharged battery. + """ + CERTIFICATION_NAME_UNSPECIFIED = 0 + ENERGY_STAR = 1 + ENERGY_STAR_MOST_EFFICIENT = 2 + EPREL = 3 + EU_ECOLABEL = 4 + VEHICLE_ENERGY_EFFICIENCY = 5 + VEHICLE_ENERGY_EFFICIENCY_DISCHARGED_BATTERY = 6 + + +class DigitalSourceType(proto.Enum): + r"""The digital source type. Following + `IPTC `__. + + Values: + DIGITAL_SOURCE_TYPE_UNSPECIFIED (0): + Digital source type is unspecified. + TRAINED_ALGORITHMIC_MEDIA (1): + Text created algorithmically using a model + derived from sampled content. + DEFAULT (2): + Text NOT created algorithmically using a + model derived from sampled content (the default) + """ + DIGITAL_SOURCE_TYPE_UNSPECIFIED = 0 + TRAINED_ALGORITHMIC_MEDIA = 1 + DEFAULT = 2 + + +class ProductAttributes(proto.Message): + r"""Product attributes. + + .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields + + Attributes: + identifier_exists (bool): + Set this value to false when the item does + not have unique product identifiers appropriate + to its category, such as GTIN, MPN, and brand. + Defaults to true, if not provided. + + This field is a member of `oneof`_ ``_identifier_exists``. + is_bundle (bool): + Whether the item is a business-defined sub-API. A [sub-API] + (https://support.google.com/merchants/answer/6324449) is a + custom grouping of different products sold by a business for + a single price. + + This field is a member of `oneof`_ ``_is_bundle``. + title (str): + Title of the item. + + This field is a member of `oneof`_ ``_title``. + description (str): + Description of the item. + + This field is a member of `oneof`_ ``_description``. + link (str): + URL directly linking to your item's page on + your online store. + + This field is a member of `oneof`_ ``_link``. + mobile_link (str): + URL for the mobile-optimized version of your + item's landing page. + + This field is a member of `oneof`_ ``_mobile_link``. + canonical_link (str): + URL for the canonical version of your item's + landing page. + + This field is a member of `oneof`_ ``_canonical_link``. + image_link (str): + URL of an image of the item. + + This field is a member of `oneof`_ ``_image_link``. + additional_image_links (MutableSequence[str]): + Additional URLs of images of the item. + expiration_date (google.protobuf.timestamp_pb2.Timestamp): + Date on which the item should expire, as specified upon + insertion, in `ISO + 8601 `__ format. The + actual expiration date is exposed in ``productstatuses`` as + `googleExpirationDate `__ + and might be earlier if ``expirationDate`` is too far in the + future. + disclosure_date (google.protobuf.timestamp_pb2.Timestamp): + The date time when an offer becomes visible in search + results across Google’s YouTube surfaces, in `ISO + 8601 `__ format. See + `Disclosure + date `__ + for more information. + adult (bool): + Set to true if the item is targeted towards + adults. + + This field is a member of `oneof`_ ``_adult``. + age_group (google.shopping.merchant_products_v1.types.AgeGroup): + Target `age + group `__ + of the item. + + This field is a member of `oneof`_ ``_age_group``. + availability (google.shopping.merchant_products_v1.types.Availability): + `Availability `__ + status of the item. + + This field is a member of `oneof`_ ``_availability``. + availability_date (google.protobuf.timestamp_pb2.Timestamp): + The day a pre-ordered product becomes available for + delivery, in `ISO + 8601 `__ format. + brand (str): + `Brand `__ + of the item. For example, "Google". + + This field is a member of `oneof`_ ``_brand``. + color (str): + `Color `__ + of the item. For example, "red". + + This field is a member of `oneof`_ ``_color``. + condition (google.shopping.merchant_products_v1.types.Condition): + `Condition `__ + or state of the item. + + This field is a member of `oneof`_ ``_condition``. + gender (google.shopping.merchant_products_v1.types.Gender): + Target + `gender `__ + of the item. + + This field is a member of `oneof`_ ``_gender``. + google_product_category (str): + Google's category of the item (see `Google product + taxonomy `__). + When querying products, this field will contain the user + provided value. There is currently no way to get back the + auto assigned google product categories through the API. + + This field is a member of `oneof`_ ``_google_product_category``. + gtins (MutableSequence[str]): + Global Trade Item Numbers + (`GTIN `__) + of the item. You can provide up to 10 GTINs. + item_group_id (str): + Shared identifier for all variants of the + same product. + + This field is a member of `oneof`_ ``_item_group_id``. + material (str): + The + `material `__ + of which the item is made. For example, "Leather" or + "Cotton". + + This field is a member of `oneof`_ ``_material``. + mpn (str): + Manufacturer Part Number + (`MPN `__) + of the item. + + This field is a member of `oneof`_ ``_mpn``. + pattern (str): + The item's + `pattern `__. + For example, polka dots. + + This field is a member of `oneof`_ ``_pattern``. + price (google.shopping.type.types.Price): + Price of the item. + maximum_retail_price (google.shopping.type.types.Price): + Maximum retail price (MRP) of the item. + Applicable to India only. + installment (google.shopping.merchant_products_v1.types.ProductInstallment): + Number and amount of installments to pay for + an item. + subscription_cost (google.shopping.merchant_products_v1.types.SubscriptionCost): + Number of periods (months or years) and + amount of payment per period for an item with an + associated subscription contract. + loyalty_points (google.shopping.merchant_products_v1.types.LoyaltyPoints): + Loyalty points that users receive after + purchasing the item. Japan only. + loyalty_programs (MutableSequence[google.shopping.merchant_products_v1.types.LoyaltyProgram]): + A list of loyalty program information that is + used to surface loyalty benefits (for example, + better pricing, points, etc) to the user of this + item. + product_types (MutableSequence[str]): + Categories of the item (formatted as in `product data + specification `__). + sale_price (google.shopping.type.types.Price): + Advertised sale price of the item. + sale_price_effective_date (google.type.interval_pb2.Interval): + Date range during which the item is on sale, see `product + data + specification `__. + sell_on_google_quantity (int): + The quantity of the product that is available + for selling on Google. Supported only for online + products. + + This field is a member of `oneof`_ ``_sell_on_google_quantity``. + product_height (google.shopping.merchant_products_v1.types.ProductDimension): + The height of the product in the units + provided. The value must be between + 0 (exclusive) and 3000 (inclusive). + product_length (google.shopping.merchant_products_v1.types.ProductDimension): + The length of the product in the units + provided. The value must be between 0 + (exclusive) and 3000 (inclusive). + product_width (google.shopping.merchant_products_v1.types.ProductDimension): + The width of the product in the units + provided. The value must be between 0 + (exclusive) and 3000 (inclusive). + product_weight (google.shopping.merchant_products_v1.types.ProductWeight): + The weight of the product in the units + provided. The value must be between 0 + (exclusive) and 2000 (inclusive). + shipping (MutableSequence[google.shopping.merchant_products_v1.types.Shipping]): + Shipping rules. + free_shipping_threshold (MutableSequence[google.shopping.merchant_products_v1.types.FreeShippingThreshold]): + Conditions to be met for a product to have + free shipping. + shipping_weight (google.shopping.merchant_products_v1.types.ShippingWeight): + Weight of the item for shipping. + shipping_length (google.shopping.merchant_products_v1.types.ShippingDimension): + Length of the item for shipping. + shipping_width (google.shopping.merchant_products_v1.types.ShippingDimension): + Width of the item for shipping. + shipping_height (google.shopping.merchant_products_v1.types.ShippingDimension): + Height of the item for shipping. + max_handling_time (int): + Maximal product handling time (in business + days). + + This field is a member of `oneof`_ ``_max_handling_time``. + min_handling_time (int): + Minimal product handling time (in business + days). + + This field is a member of `oneof`_ ``_min_handling_time``. + shipping_label (str): + The shipping label of the product, used to + group product in account-level shipping rules. + + This field is a member of `oneof`_ ``_shipping_label``. + transit_time_label (str): + The transit time label of the product, used + to group product in account-level transit time + tables. + + This field is a member of `oneof`_ ``_transit_time_label``. + size (str): + Size of the item. Only one value is allowed. For variants + with different sizes, insert a separate product for each + size with the same ``itemGroupId`` value, see + `Size `__. + + This field is a member of `oneof`_ ``_size``. + size_system (google.shopping.merchant_products_v1.types.SizeSystem): + System in which the size is specified. Recommended for + apparel items. For more information, see `Size + system `__. + + This field is a member of `oneof`_ ``_size_system``. + size_types (MutableSequence[google.shopping.merchant_products_v1.types.SizeType]): + The cut of the item. It can be used to represent combined + size types for apparel items. Maximum two of size types can + be provided, see `Size + type `__. + energy_efficiency_class (google.shopping.merchant_products_v1.types.EnergyEfficiencyClass): + The `energy efficiency + class `__ + as defined in EU directive 2010/30/EU. + + This field is a member of `oneof`_ ``_energy_efficiency_class``. + min_energy_efficiency_class (google.shopping.merchant_products_v1.types.EnergyEfficiencyClass): + The `energy efficiency + class `__ + as defined in EU directive 2010/30/EU. + + This field is a member of `oneof`_ ``_min_energy_efficiency_class``. + max_energy_efficiency_class (google.shopping.merchant_products_v1.types.EnergyEfficiencyClass): + The `energy efficiency + class `__ + as defined in EU directive 2010/30/EU. + + This field is a member of `oneof`_ ``_max_energy_efficiency_class``. + unit_pricing_measure (google.shopping.merchant_products_v1.types.UnitPricingMeasure): + The measure and dimension of an item. + unit_pricing_base_measure (google.shopping.merchant_products_v1.types.UnitPricingBaseMeasure): + The preference of the denominator of the unit + price. + multipack (int): + The number of identical products in a + business-defined multipack. + + This field is a member of `oneof`_ ``_multipack``. + ads_grouping (str): + Used to group items in an arbitrary way. Only for CPA%, + discouraged otherwise. For more information, see `Display + ads + attribute `__. + + This field is a member of `oneof`_ ``_ads_grouping``. + ads_labels (MutableSequence[str]): + Similar to ads_grouping, but only works on CPC. + ads_redirect (str): + Allows advertisers to override the item URL + when the product is shown within the context of + Product ads. + + This field is a member of `oneof`_ ``_ads_redirect``. + cost_of_goods_sold (google.shopping.type.types.Price): + Cost of goods sold. Used for gross profit + reporting. + product_details (MutableSequence[google.shopping.merchant_products_v1.types.ProductDetail]): + Technical specification or additional product + details. + product_highlights (MutableSequence[str]): + Bullet points describing the most relevant `product + highlights `__. + display_ads_id (str): + An identifier for an item for dynamic + remarketing campaigns. + + This field is a member of `oneof`_ ``_display_ads_id``. + display_ads_similar_ids (MutableSequence[str]): + Advertiser-specified recommendations. For more information, + see `Display ads attribute + specification `__. + display_ads_title (str): + Title of an item for dynamic remarketing + campaigns. + + This field is a member of `oneof`_ ``_display_ads_title``. + display_ads_link (str): + URL directly to your item's landing page for + dynamic remarketing campaigns. + + This field is a member of `oneof`_ ``_display_ads_link``. + display_ads_value (float): + Offer margin for dynamic remarketing campaigns. For more + information, see `Display ads + attribute `__. + + This field is a member of `oneof`_ ``_display_ads_value``. + promotion_ids (MutableSequence[str]): + The unique ID of a promotion. + pickup_method (google.shopping.merchant_products_v1.types.PickupMethod): + The + `pickup `__ + option for the item. + + This field is a member of `oneof`_ ``_pickup_method``. + pickup_sla (google.shopping.merchant_products_v1.types.PickupSla): + Item store pickup timeline. For more information, see + `Pickup + SLA `__. + + This field is a member of `oneof`_ ``_pickup_sla``. + link_template (str): + `Link + template `__ + for business hosted local storefront. + + This field is a member of `oneof`_ ``_link_template``. + mobile_link_template (str): + `Link + template `__ + for business hosted local storefront optimized for mobile + devices. + + This field is a member of `oneof`_ ``_mobile_link_template``. + custom_label_0 (str): + `Custom label + 0 `__ + for custom grouping of items in a Shopping campaign. + + This field is a member of `oneof`_ ``_custom_label_0``. + custom_label_1 (str): + `Custom label + 1 `__ + for custom grouping of items in a Shopping campaign. + + This field is a member of `oneof`_ ``_custom_label_1``. + custom_label_2 (str): + `Custom label + 2 `__ + for custom grouping of items in a Shopping campaign. + + This field is a member of `oneof`_ ``_custom_label_2``. + custom_label_3 (str): + `Custom label + 3 `__ + for custom grouping of items in a Shopping campaign. + + This field is a member of `oneof`_ ``_custom_label_3``. + custom_label_4 (str): + `Custom label + 4 `__ + for custom grouping of items in a Shopping campaign. + + This field is a member of `oneof`_ ``_custom_label_4``. + included_destinations (MutableSequence[google.shopping.type.types.Destination.DestinationEnum]): + The list of destinations to include for this target + (corresponds to checked check boxes in Merchant Center). + Default destinations are always included unless provided in + ``excludedDestinations``. + + For more information, see `Included + destination `__. + + Note: We recommend setting destinations on datasources level + for most use cases. Use this field within products to only + setup exceptions. + excluded_destinations (MutableSequence[google.shopping.type.types.Destination.DestinationEnum]): + The list of destinations to exclude for this target + (corresponds to unchecked check boxes in Merchant Center). + + For more information, see `Excluded + destination `__. + + Note: We recommend setting destinations on datasources level + for most use cases. Use this field within products to only + setup exceptions. + shopping_ads_excluded_countries (MutableSequence[str]): + List of country codes `(ISO 3166-1 + alpha-2) `__ + to exclude the offer from Shopping Ads destination. + Countries from this list are removed from countries + configured in data source settings. + external_seller_id (str): + Required for multi-seller accounts. Use this + attribute if you're a marketplace uploading + products for various sellers to your + multi-seller account. + + This field is a member of `oneof`_ ``_external_seller_id``. + pause (google.shopping.merchant_products_v1.types.Pause): + Publication of this item will be temporarily + `paused `__. + + This field is a member of `oneof`_ ``_pause``. + lifestyle_image_links (MutableSequence[str]): + Additional URLs of lifestyle images of the item, used to + explicitly identify images that showcase your item in a + real-world context. See the `Help Center + article `__ + for more information. + cloud_export_additional_properties (MutableSequence[google.shopping.merchant_products_v1.types.CloudExportAdditionalProperties]): + Extra fields to export to the Cloud Retail + program. + virtual_model_link (str): + URL of the 3D image of the item. See the `Help Center + article `__ + for more information. + + This field is a member of `oneof`_ ``_virtual_model_link``. + certifications (MutableSequence[google.shopping.merchant_products_v1.types.ProductCertification]): + Product Certifications, for example for energy efficiency + labeling of products recorded in the `EU + EPREL `__ database. + See the `Help + Center `__ + article for more information. + structured_title (google.shopping.merchant_products_v1.types.StructuredTitle): + Structured title, for algorithmically + (AI)-generated titles. + + This field is a member of `oneof`_ ``_structured_title``. + structured_description (google.shopping.merchant_products_v1.types.StructuredDescription): + Structured description, for algorithmically + (AI)-generated descriptions. + + This field is a member of `oneof`_ ``_structured_description``. + auto_pricing_min_price (google.shopping.type.types.Price): + A safeguard in the [automated discounts] + (https://support.google.com/merchants/answer/10295759) and + "Dynamic Promotions" + (https://support.google.com/merchants/answer/13949249) + projects, ensuring that discounts on business offers do not + fall below this value, thereby preserving the offer's value + and profitability. + sustainability_incentives (MutableSequence[google.shopping.merchant_products_v1.types.ProductSustainabilityIncentive]): + The list of sustainability incentive + programs. + """ + + identifier_exists: bool = proto.Field( + proto.BOOL, + number=4, + optional=True, + ) + is_bundle: bool = proto.Field( + proto.BOOL, + number=5, + optional=True, + ) + title: str = proto.Field( + proto.STRING, + number=6, + optional=True, + ) + description: str = proto.Field( + proto.STRING, + number=7, + optional=True, + ) + link: str = proto.Field( + proto.STRING, + number=8, + optional=True, + ) + mobile_link: str = proto.Field( + proto.STRING, + number=9, + optional=True, + ) + canonical_link: str = proto.Field( + proto.STRING, + number=10, + optional=True, + ) + image_link: str = proto.Field( + proto.STRING, + number=11, + optional=True, + ) + additional_image_links: MutableSequence[str] = proto.RepeatedField( + proto.STRING, + number=12, + ) + expiration_date: timestamp_pb2.Timestamp = proto.Field( + proto.MESSAGE, + number=16, + message=timestamp_pb2.Timestamp, + ) + disclosure_date: timestamp_pb2.Timestamp = proto.Field( + proto.MESSAGE, + number=79, + message=timestamp_pb2.Timestamp, + ) + adult: bool = proto.Field( + proto.BOOL, + number=17, + optional=True, + ) + age_group: "AgeGroup" = proto.Field( + proto.ENUM, + number=18, + optional=True, + enum="AgeGroup", + ) + availability: "Availability" = proto.Field( + proto.ENUM, + number=19, + optional=True, + enum="Availability", + ) + availability_date: timestamp_pb2.Timestamp = proto.Field( + proto.MESSAGE, + number=20, + message=timestamp_pb2.Timestamp, + ) + brand: str = proto.Field( + proto.STRING, + number=21, + optional=True, + ) + color: str = proto.Field( + proto.STRING, + number=22, + optional=True, + ) + condition: "Condition" = proto.Field( + proto.ENUM, + number=23, + optional=True, + enum="Condition", + ) + gender: "Gender" = proto.Field( + proto.ENUM, + number=24, + optional=True, + enum="Gender", + ) + google_product_category: str = proto.Field( + proto.STRING, + number=25, + optional=True, + ) + gtins: MutableSequence[str] = proto.RepeatedField( + proto.STRING, + number=140, + ) + item_group_id: str = proto.Field( + proto.STRING, + number=27, + optional=True, + ) + material: str = proto.Field( + proto.STRING, + number=28, + optional=True, + ) + mpn: str = proto.Field( + proto.STRING, + number=29, + optional=True, + ) + pattern: str = proto.Field( + proto.STRING, + number=30, + optional=True, + ) + price: types.Price = proto.Field( + proto.MESSAGE, + number=31, + message=types.Price, + ) + maximum_retail_price: types.Price = proto.Field( + proto.MESSAGE, + number=139, + message=types.Price, + ) + installment: "ProductInstallment" = proto.Field( + proto.MESSAGE, + number=32, + message="ProductInstallment", + ) + subscription_cost: "SubscriptionCost" = proto.Field( + proto.MESSAGE, + number=33, + message="SubscriptionCost", + ) + loyalty_points: "LoyaltyPoints" = proto.Field( + proto.MESSAGE, + number=34, + message="LoyaltyPoints", + ) + loyalty_programs: MutableSequence["LoyaltyProgram"] = proto.RepeatedField( + proto.MESSAGE, + number=136, + message="LoyaltyProgram", + ) + product_types: MutableSequence[str] = proto.RepeatedField( + proto.STRING, + number=35, + ) + sale_price: types.Price = proto.Field( + proto.MESSAGE, + number=36, + message=types.Price, + ) + sale_price_effective_date: interval_pb2.Interval = proto.Field( + proto.MESSAGE, + number=37, + message=interval_pb2.Interval, + ) + sell_on_google_quantity: int = proto.Field( + proto.INT64, + number=38, + optional=True, + ) + product_height: "ProductDimension" = proto.Field( + proto.MESSAGE, + number=119, + message="ProductDimension", + ) + product_length: "ProductDimension" = proto.Field( + proto.MESSAGE, + number=120, + message="ProductDimension", + ) + product_width: "ProductDimension" = proto.Field( + proto.MESSAGE, + number=121, + message="ProductDimension", + ) + product_weight: "ProductWeight" = proto.Field( + proto.MESSAGE, + number=122, + message="ProductWeight", + ) + shipping: MutableSequence["Shipping"] = proto.RepeatedField( + proto.MESSAGE, + number=39, + message="Shipping", + ) + free_shipping_threshold: MutableSequence[ + "FreeShippingThreshold" + ] = proto.RepeatedField( + proto.MESSAGE, + number=135, + message="FreeShippingThreshold", + ) + shipping_weight: "ShippingWeight" = proto.Field( + proto.MESSAGE, + number=40, + message="ShippingWeight", + ) + shipping_length: "ShippingDimension" = proto.Field( + proto.MESSAGE, + number=41, + message="ShippingDimension", + ) + shipping_width: "ShippingDimension" = proto.Field( + proto.MESSAGE, + number=42, + message="ShippingDimension", + ) + shipping_height: "ShippingDimension" = proto.Field( + proto.MESSAGE, + number=43, + message="ShippingDimension", + ) + max_handling_time: int = proto.Field( + proto.INT64, + number=44, + optional=True, + ) + min_handling_time: int = proto.Field( + proto.INT64, + number=45, + optional=True, + ) + shipping_label: str = proto.Field( + proto.STRING, + number=46, + optional=True, + ) + transit_time_label: str = proto.Field( + proto.STRING, + number=47, + optional=True, + ) + size: str = proto.Field( + proto.STRING, + number=48, + optional=True, + ) + size_system: "SizeSystem" = proto.Field( + proto.ENUM, + number=49, + optional=True, + enum="SizeSystem", + ) + size_types: MutableSequence["SizeType"] = proto.RepeatedField( + proto.ENUM, + number=50, + enum="SizeType", + ) + energy_efficiency_class: "EnergyEfficiencyClass" = proto.Field( + proto.ENUM, + number=53, + optional=True, + enum="EnergyEfficiencyClass", + ) + min_energy_efficiency_class: "EnergyEfficiencyClass" = proto.Field( + proto.ENUM, + number=54, + optional=True, + enum="EnergyEfficiencyClass", + ) + max_energy_efficiency_class: "EnergyEfficiencyClass" = proto.Field( + proto.ENUM, + number=55, + optional=True, + enum="EnergyEfficiencyClass", + ) + unit_pricing_measure: "UnitPricingMeasure" = proto.Field( + proto.MESSAGE, + number=56, + message="UnitPricingMeasure", + ) + unit_pricing_base_measure: "UnitPricingBaseMeasure" = proto.Field( + proto.MESSAGE, + number=57, + message="UnitPricingBaseMeasure", + ) + multipack: int = proto.Field( + proto.INT64, + number=58, + optional=True, + ) + ads_grouping: str = proto.Field( + proto.STRING, + number=59, + optional=True, + ) + ads_labels: MutableSequence[str] = proto.RepeatedField( + proto.STRING, + number=60, + ) + ads_redirect: str = proto.Field( + proto.STRING, + number=61, + optional=True, + ) + cost_of_goods_sold: types.Price = proto.Field( + proto.MESSAGE, + number=62, + message=types.Price, + ) + product_details: MutableSequence["ProductDetail"] = proto.RepeatedField( + proto.MESSAGE, + number=63, + message="ProductDetail", + ) + product_highlights: MutableSequence[str] = proto.RepeatedField( + proto.STRING, + number=64, + ) + display_ads_id: str = proto.Field( + proto.STRING, + number=65, + optional=True, + ) + display_ads_similar_ids: MutableSequence[str] = proto.RepeatedField( + proto.STRING, + number=66, + ) + display_ads_title: str = proto.Field( + proto.STRING, + number=67, + optional=True, + ) + display_ads_link: str = proto.Field( + proto.STRING, + number=68, + optional=True, + ) + display_ads_value: float = proto.Field( + proto.DOUBLE, + number=69, + optional=True, + ) + promotion_ids: MutableSequence[str] = proto.RepeatedField( + proto.STRING, + number=70, + ) + pickup_method: "PickupMethod" = proto.Field( + proto.ENUM, + number=80, + optional=True, + enum="PickupMethod", + ) + pickup_sla: "PickupSla" = proto.Field( + proto.ENUM, + number=81, + optional=True, + enum="PickupSla", + ) + link_template: str = proto.Field( + proto.STRING, + number=82, + optional=True, + ) + mobile_link_template: str = proto.Field( + proto.STRING, + number=83, + optional=True, + ) + custom_label_0: str = proto.Field( + proto.STRING, + number=71, + optional=True, + ) + custom_label_1: str = proto.Field( + proto.STRING, + number=72, + optional=True, + ) + custom_label_2: str = proto.Field( + proto.STRING, + number=73, + optional=True, + ) + custom_label_3: str = proto.Field( + proto.STRING, + number=74, + optional=True, + ) + custom_label_4: str = proto.Field( + proto.STRING, + number=75, + optional=True, + ) + included_destinations: MutableSequence[ + types.Destination.DestinationEnum + ] = proto.RepeatedField( + proto.ENUM, + number=76, + enum=types.Destination.DestinationEnum, + ) + excluded_destinations: MutableSequence[ + types.Destination.DestinationEnum + ] = proto.RepeatedField( + proto.ENUM, + number=77, + enum=types.Destination.DestinationEnum, + ) + shopping_ads_excluded_countries: MutableSequence[str] = proto.RepeatedField( + proto.STRING, + number=78, + ) + external_seller_id: str = proto.Field( + proto.STRING, + number=1, + optional=True, + ) + pause: "Pause" = proto.Field( + proto.ENUM, + number=13, + optional=True, + enum="Pause", + ) + lifestyle_image_links: MutableSequence[str] = proto.RepeatedField( + proto.STRING, + number=14, + ) + cloud_export_additional_properties: MutableSequence[ + "CloudExportAdditionalProperties" + ] = proto.RepeatedField( + proto.MESSAGE, + number=84, + message="CloudExportAdditionalProperties", + ) + virtual_model_link: str = proto.Field( + proto.STRING, + number=130, + optional=True, + ) + certifications: MutableSequence["ProductCertification"] = proto.RepeatedField( + proto.MESSAGE, + number=123, + message="ProductCertification", + ) + structured_title: "StructuredTitle" = proto.Field( + proto.MESSAGE, + number=132, + optional=True, + message="StructuredTitle", + ) + structured_description: "StructuredDescription" = proto.Field( + proto.MESSAGE, + number=133, + optional=True, + message="StructuredDescription", + ) + auto_pricing_min_price: types.Price = proto.Field( + proto.MESSAGE, + number=124, + message=types.Price, + ) + sustainability_incentives: MutableSequence[ + "ProductSustainabilityIncentive" + ] = proto.RepeatedField( + proto.MESSAGE, + number=138, + message="ProductSustainabilityIncentive", + ) + + +class ShippingWeight(proto.Message): + r"""The ShippingWeight of the product. + + Attributes: + value (float): + The weight of the product used to calculate + the shipping cost of the item. + unit (str): + The unit of value. + """ + + value: float = proto.Field( + proto.DOUBLE, + number=1, + ) + unit: str = proto.Field( + proto.STRING, + number=2, + ) + + +class ShippingDimension(proto.Message): + r"""The ShippingDimension of the product. + + Attributes: + value (float): + The dimension of the product used to + calculate the shipping cost of the item. + unit (str): + The unit of value. + """ + + value: float = proto.Field( + proto.DOUBLE, + number=1, + ) + unit: str = proto.Field( + proto.STRING, + number=2, + ) + + +class UnitPricingBaseMeasure(proto.Message): + r"""The UnitPricingBaseMeasure of the product. + + Attributes: + value (int): + The denominator of the unit price. + unit (str): + The unit of the denominator. + """ + + value: int = proto.Field( + proto.INT64, + number=1, + ) + unit: str = proto.Field( + proto.STRING, + number=2, + ) + + +class UnitPricingMeasure(proto.Message): + r"""The UnitPricingMeasure of the product. + + Attributes: + value (float): + The measure of an item. + unit (str): + The unit of the measure. + """ + + value: float = proto.Field( + proto.DOUBLE, + number=1, + ) + unit: str = proto.Field( + proto.STRING, + number=2, + ) + + +class SubscriptionCost(proto.Message): + r"""The SubscriptionCost of the product. + + Attributes: + period (google.shopping.merchant_products_v1.types.SubscriptionPeriod): + The type of subscription period. Supported values are: + + - "``month``" + - "``year``". + period_length (int): + The number of subscription periods the buyer + has to pay. + amount (google.shopping.type.types.Price): + The amount the buyer has to pay per + subscription period. + """ + + period: "SubscriptionPeriod" = proto.Field( + proto.ENUM, + number=1, + enum="SubscriptionPeriod", + ) + period_length: int = proto.Field( + proto.INT64, + number=2, + ) + amount: types.Price = proto.Field( + proto.MESSAGE, + number=3, + message=types.Price, + ) + + +class ProductInstallment(proto.Message): + r"""A message that represents installment. + + .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields + + Attributes: + months (int): + The number of installments the buyer has to + pay. + amount (google.shopping.type.types.Price): + The amount the buyer has to pay per month. + downpayment (google.shopping.type.types.Price): + The up-front down payment amount the buyer + has to pay. + + This field is a member of `oneof`_ ``_downpayment``. + credit_type (google.shopping.merchant_products_v1.types.CreditType): + Type of installment payments. + + This field is a member of `oneof`_ ``_credit_type``. + """ + + months: int = proto.Field( + proto.INT64, + number=1, + ) + amount: types.Price = proto.Field( + proto.MESSAGE, + number=2, + message=types.Price, + ) + downpayment: types.Price = proto.Field( + proto.MESSAGE, + number=3, + optional=True, + message=types.Price, + ) + credit_type: "CreditType" = proto.Field( + proto.ENUM, + number=4, + optional=True, + enum="CreditType", + ) + + +class LoyaltyPoints(proto.Message): + r"""A message that represents loyalty points. + + Attributes: + name (str): + Name of loyalty points program. It is + recommended to limit the name to 12 full-width + characters or 24 Roman characters. + points_value (int): + The retailer's loyalty points in absolute + value. + ratio (float): + The ratio of a point when converted to + currency. Google assumes currency based on + Merchant Center settings. If ratio is left out, + it defaults to 1.0. + """ + + name: str = proto.Field( + proto.STRING, + number=1, + ) + points_value: int = proto.Field( + proto.INT64, + number=2, + ) + ratio: float = proto.Field( + proto.DOUBLE, + number=3, + ) + + +class LoyaltyProgram(proto.Message): + r"""A message that represents loyalty program. + + .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields + + Attributes: + program_label (str): + The label of the loyalty program. This is an + internal label that uniquely identifies the + relationship between a business entity and a + loyalty program entity. The label must be + provided so that the system can associate the + assets below (for example, price and points) + with a business. The corresponding program must + be linked to the Merchant Center account. + + This field is a member of `oneof`_ ``_program_label``. + tier_label (str): + The label of the tier within the loyalty + program. Must match one of the labels within the + program. + + This field is a member of `oneof`_ ``_tier_label``. + price (google.shopping.type.types.Price): + The price for members of the given tier, that + is, the instant discount price. Must be smaller + or equal to the regular price. + + This field is a member of `oneof`_ ``_price``. + cashback_for_future_use (google.shopping.type.types.Price): + The cashback that can be used for future + purchases. + + This field is a member of `oneof`_ ``_cashback_for_future_use``. + loyalty_points (int): + The amount of loyalty points earned on a + purchase. + + This field is a member of `oneof`_ ``_loyalty_points``. + member_price_effective_date (google.type.interval_pb2.Interval): + A date range during which the item is + eligible for member price. If not specified, the + member price is always applicable. The date + range is represented by a pair of ISO 8601 dates + separated by a space, comma, or slash. + + This field is a member of `oneof`_ ``_member_price_effective_date``. + shipping_label (str): + The label of the shipping benefit. If the + field has value, this offer has loyalty shipping + benefit. If the field value isn't provided, the + item is not eligible for loyalty shipping for + the given loyalty tier. + + This field is a member of `oneof`_ ``_shipping_label``. + """ + + program_label: str = proto.Field( + proto.STRING, + number=1, + optional=True, + ) + tier_label: str = proto.Field( + proto.STRING, + number=2, + optional=True, + ) + price: types.Price = proto.Field( + proto.MESSAGE, + number=3, + optional=True, + message=types.Price, + ) + cashback_for_future_use: types.Price = proto.Field( + proto.MESSAGE, + number=4, + optional=True, + message=types.Price, + ) + loyalty_points: int = proto.Field( + proto.INT64, + number=5, + optional=True, + ) + member_price_effective_date: interval_pb2.Interval = proto.Field( + proto.MESSAGE, + number=6, + optional=True, + message=interval_pb2.Interval, + ) + shipping_label: str = proto.Field( + proto.STRING, + number=7, + optional=True, + ) + + +class Shipping(proto.Message): + r"""The Shipping of the product. + + .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields + + Attributes: + price (google.shopping.type.types.Price): + Fixed shipping price, represented as a + number. + country (str): + The `CLDR territory + code `__ + of the country to which an item will ship. + region (str): + The geographic region to which a shipping rate applies. See + `region `__ + for more information. + service (str): + A free-form description of the service class + or delivery speed. + location_id (int): + The numeric ID of a location that the shipping rate applies + to as defined in the `AdWords + API `__. + location_group_name (str): + The location where the shipping is + applicable, represented by a location group + name. + postal_code (str): + The postal code range that the shipping rate applies to, + represented by a postal code, a postal code prefix followed + by a \* wildcard, a range between two postal codes or two + postal code prefixes of equal length. + min_handling_time (int): + Minimum handling time (inclusive) between when the order is + received and shipped in business days. 0 means that the + order is shipped on the same day as it is received if it + happens before the cut-off time. + [minHandlingTime][google.shopping.merchant.products.v1.Shipping.min_handling_time] + can only be present together with + [maxHandlingTime][google.shopping.merchant.products.v1.Shipping.max_handling_time]; + but it is not required if + [maxHandlingTime][google.shopping.merchant.products.v1.Shipping.max_handling_time] + is present. + + This field is a member of `oneof`_ ``_min_handling_time``. + max_handling_time (int): + Maximum handling time (inclusive) between when the order is + received and shipped in business days. 0 means that the + order is shipped on the same day as it is received if it + happens before the cut-off time. Both + [maxHandlingTime][google.shopping.merchant.products.v1.Shipping.max_handling_time] + and + [maxTransitTime][google.shopping.merchant.products.v1.Shipping.max_transit_time] + are required if providing shipping speeds. + [minHandlingTime][google.shopping.merchant.products.v1.Shipping.min_handling_time] + is optional if + [maxHandlingTime][google.shopping.merchant.products.v1.Shipping.max_handling_time] + is present. + + This field is a member of `oneof`_ ``_max_handling_time``. + min_transit_time (int): + Minimum transit time (inclusive) between when the order has + shipped and when it is delivered in business days. 0 means + that the order is delivered on the same day as it ships. + [minTransitTime][google.shopping.merchant.products.v1.Shipping.min_transit_time] + can only be present together with + [maxTransitTime][google.shopping.merchant.products.v1.Shipping.max_transit_time]; + but it is not required if + [maxTransitTime][google.shopping.merchant.products.v1.Shipping.max_transit_time] + is present. + + This field is a member of `oneof`_ ``_min_transit_time``. + max_transit_time (int): + Maximum transit time (inclusive) between when the order has + shipped and when it is delivered in business days. 0 means + that the order is delivered on the same day as it ships. + Both + [maxHandlingTime][google.shopping.merchant.products.v1.Shipping.max_handling_time] + and + [maxTransitTime][google.shopping.merchant.products.v1.Shipping.max_transit_time] + are required if providing shipping speeds. + [minTransitTime][google.shopping.merchant.products.v1.Shipping.min_transit_time] + is optional if + [maxTransitTime][google.shopping.merchant.products.v1.Shipping.max_transit_time] + is present. + + This field is a member of `oneof`_ ``_max_transit_time``. + """ + + price: types.Price = proto.Field( + proto.MESSAGE, + number=1, + message=types.Price, + ) + country: str = proto.Field( + proto.STRING, + number=2, + ) + region: str = proto.Field( + proto.STRING, + number=3, + ) + service: str = proto.Field( + proto.STRING, + number=4, + ) + location_id: int = proto.Field( + proto.INT64, + number=5, + ) + location_group_name: str = proto.Field( + proto.STRING, + number=6, + ) + postal_code: str = proto.Field( + proto.STRING, + number=7, + ) + min_handling_time: int = proto.Field( + proto.INT64, + number=8, + optional=True, + ) + max_handling_time: int = proto.Field( + proto.INT64, + number=9, + optional=True, + ) + min_transit_time: int = proto.Field( + proto.INT64, + number=10, + optional=True, + ) + max_transit_time: int = proto.Field( + proto.INT64, + number=11, + optional=True, + ) + + +class FreeShippingThreshold(proto.Message): + r"""Conditions to be met for a product to have free shipping. + + .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields + + Attributes: + country (str): + The `CLDR territory + code `__ + of the country to which an item will ship. + + This field is a member of `oneof`_ ``_country``. + price_threshold (google.shopping.type.types.Price): + The minimum product price for the shipping + cost to become free. Represented as a number. + + This field is a member of `oneof`_ ``_price_threshold``. + """ + + country: str = proto.Field( + proto.STRING, + number=1, + optional=True, + ) + price_threshold: types.Price = proto.Field( + proto.MESSAGE, + number=2, + optional=True, + message=types.Price, + ) + + +class ProductDetail(proto.Message): + r"""The product details. + + Attributes: + section_name (str): + The section header used to group a set of + product details. + attribute_name (str): + The name of the product detail. + attribute_value (str): + The value of the product detail. + """ + + section_name: str = proto.Field( + proto.STRING, + number=1, + ) + attribute_name: str = proto.Field( + proto.STRING, + number=2, + ) + attribute_value: str = proto.Field( + proto.STRING, + number=3, + ) + + +class ProductCertification(proto.Message): + r"""Product + `certification `__, + initially introduced for EU energy efficiency labeling compliance + using the EU EPREL database. + + + .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields + + Attributes: + certification_authority (google.shopping.merchant_products_v1.types.CertificationAuthority): + The certification authority. + + This field is a member of `oneof`_ ``_certification_authority``. + certification_name (google.shopping.merchant_products_v1.types.CertificationName): + The name of the certification. + + This field is a member of `oneof`_ ``_certification_name``. + certification_code (str): + The certification code. + Maximum length is 2000 characters. + + This field is a member of `oneof`_ ``_certification_code``. + certification_value (str): + The certification value (also known as class, + level or grade), for example "A+", "C", "gold". + Maximum length is 2000 characters. + + This field is a member of `oneof`_ ``_certification_value``. + """ + + certification_authority: "CertificationAuthority" = proto.Field( + proto.ENUM, + number=1, + optional=True, + enum="CertificationAuthority", + ) + certification_name: "CertificationName" = proto.Field( + proto.ENUM, + number=2, + optional=True, + enum="CertificationName", + ) + certification_code: str = proto.Field( + proto.STRING, + number=3, + optional=True, + ) + certification_value: str = proto.Field( + proto.STRING, + number=4, + optional=True, + ) + + +class StructuredTitle(proto.Message): + r"""Structured title, for algorithmically (AI)-generated titles. + + .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields + + Attributes: + digital_source_type (google.shopping.merchant_products_v1.types.DigitalSourceType): + The digital source type. Following + `IPTC `__. + + This field is a member of `oneof`_ ``_digital_source_type``. + content (str): + The title text + Maximum length is 150 characters + + This field is a member of `oneof`_ ``_content``. + """ + + digital_source_type: "DigitalSourceType" = proto.Field( + proto.ENUM, + number=1, + optional=True, + enum="DigitalSourceType", + ) + content: str = proto.Field( + proto.STRING, + number=2, + optional=True, + ) + + +class StructuredDescription(proto.Message): + r"""Structured description, for algorithmically (AI)-generated + descriptions. + + + .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields + + Attributes: + digital_source_type (google.shopping.merchant_products_v1.types.DigitalSourceType): + The digital source type. Following + `IPTC `__. + + This field is a member of `oneof`_ ``_digital_source_type``. + content (str): + The description text + Maximum length is 5000 characters + + This field is a member of `oneof`_ ``_content``. + """ + + digital_source_type: "DigitalSourceType" = proto.Field( + proto.ENUM, + number=1, + optional=True, + enum="DigitalSourceType", + ) + content: str = proto.Field( + proto.STRING, + number=2, + optional=True, + ) + + +class ProductDimension(proto.Message): + r"""The dimension of the product. + + Attributes: + value (float): + Required. The dimension value represented as + a number. The value can have a maximum precision + of four decimal places. + unit (str): + Required. The dimension units. Acceptable values are: + + - "``in``" + - "``cm``". + """ + + value: float = proto.Field( + proto.DOUBLE, + number=1, + ) + unit: str = proto.Field( + proto.STRING, + number=2, + ) + + +class ProductWeight(proto.Message): + r"""The weight of the product. + + Attributes: + value (float): + Required. The weight represented as a number. + The weight can have a maximum precision of four + decimal places. + unit (str): + Required. The weight unit. Acceptable values are: + + - "``g``" + - "``kg``" + - "``oz``" + - "``lb``". + """ + + value: float = proto.Field( + proto.DOUBLE, + number=1, + ) + unit: str = proto.Field( + proto.STRING, + number=2, + ) + + +class ProductStatus(proto.Message): + r"""The status of a product, data validation issues, that is, + information about a product computed asynchronously. + + Attributes: + destination_statuses (MutableSequence[google.shopping.merchant_products_v1.types.ProductStatus.DestinationStatus]): + The intended destinations for the product. + item_level_issues (MutableSequence[google.shopping.merchant_products_v1.types.ProductStatus.ItemLevelIssue]): + A list of all issues associated with the + product. + creation_date (google.protobuf.timestamp_pb2.Timestamp): + Date on which the item has been created, in `ISO + 8601 `__ format. + last_update_date (google.protobuf.timestamp_pb2.Timestamp): + Date on which the item has been last updated, in `ISO + 8601 `__ format. + google_expiration_date (google.protobuf.timestamp_pb2.Timestamp): + Date on which the item expires, in `ISO + 8601 `__ format. + """ + + class DestinationStatus(proto.Message): + r"""The destination status of the product status. + + Attributes: + reporting_context (google.shopping.type.types.ReportingContext.ReportingContextEnum): + The name of the reporting context. + approved_countries (MutableSequence[str]): + List of country codes (ISO 3166-1 alpha-2) + where the offer is approved. + pending_countries (MutableSequence[str]): + List of country codes (ISO 3166-1 alpha-2) + where the offer is pending approval. + disapproved_countries (MutableSequence[str]): + List of country codes (ISO 3166-1 alpha-2) + where the offer is disapproved. + """ + + reporting_context: types.ReportingContext.ReportingContextEnum = proto.Field( + proto.ENUM, + number=1, + enum=types.ReportingContext.ReportingContextEnum, + ) + approved_countries: MutableSequence[str] = proto.RepeatedField( + proto.STRING, + number=2, + ) + pending_countries: MutableSequence[str] = proto.RepeatedField( + proto.STRING, + number=3, + ) + disapproved_countries: MutableSequence[str] = proto.RepeatedField( + proto.STRING, + number=4, + ) + + class ItemLevelIssue(proto.Message): + r"""The ItemLevelIssue of the product status. + + Attributes: + code (str): + The error code of the issue. + severity (google.shopping.merchant_products_v1.types.ProductStatus.ItemLevelIssue.Severity): + How this issue affects serving of the offer. + resolution (str): + Whether the issue can be resolved by the + business. + attribute (str): + The attribute's name, if the issue is caused + by a single attribute. + reporting_context (google.shopping.type.types.ReportingContext.ReportingContextEnum): + The reporting context the issue applies to. + description (str): + A short issue description in English. + detail (str): + A detailed issue description in English. + documentation (str): + The URL of a web page to help with resolving + this issue. + applicable_countries (MutableSequence[str]): + List of country codes (ISO 3166-1 alpha-2) + where issue applies to the offer. + """ + + class Severity(proto.Enum): + r"""How the issue affects the serving of the product. + + Values: + SEVERITY_UNSPECIFIED (0): + Not specified. + NOT_IMPACTED (1): + This issue represents a warning and does not + have a direct affect on the product. + DEMOTED (2): + The product is demoted and most likely have + limited performance in search results + DISAPPROVED (3): + Issue disapproves the product. + """ + SEVERITY_UNSPECIFIED = 0 + NOT_IMPACTED = 1 + DEMOTED = 2 + DISAPPROVED = 3 + + code: str = proto.Field( + proto.STRING, + number=1, + ) + severity: "ProductStatus.ItemLevelIssue.Severity" = proto.Field( + proto.ENUM, + number=2, + enum="ProductStatus.ItemLevelIssue.Severity", + ) + resolution: str = proto.Field( + proto.STRING, + number=3, + ) + attribute: str = proto.Field( + proto.STRING, + number=4, + ) + reporting_context: types.ReportingContext.ReportingContextEnum = proto.Field( + proto.ENUM, + number=5, + enum=types.ReportingContext.ReportingContextEnum, + ) + description: str = proto.Field( + proto.STRING, + number=6, + ) + detail: str = proto.Field( + proto.STRING, + number=7, + ) + documentation: str = proto.Field( + proto.STRING, + number=8, + ) + applicable_countries: MutableSequence[str] = proto.RepeatedField( + proto.STRING, + number=9, + ) + + destination_statuses: MutableSequence[DestinationStatus] = proto.RepeatedField( + proto.MESSAGE, + number=3, + message=DestinationStatus, + ) + item_level_issues: MutableSequence[ItemLevelIssue] = proto.RepeatedField( + proto.MESSAGE, + number=4, + message=ItemLevelIssue, + ) + creation_date: timestamp_pb2.Timestamp = proto.Field( + proto.MESSAGE, + number=5, + message=timestamp_pb2.Timestamp, + ) + last_update_date: timestamp_pb2.Timestamp = proto.Field( + proto.MESSAGE, + number=6, + message=timestamp_pb2.Timestamp, + ) + google_expiration_date: timestamp_pb2.Timestamp = proto.Field( + proto.MESSAGE, + number=7, + message=timestamp_pb2.Timestamp, + ) + + +class CloudExportAdditionalProperties(proto.Message): + r"""Product property for the Cloud Retail API. + For example, properties for a TV product could be + "Screen-Resolution" or "Screen-Size". + + + .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields + + Attributes: + property_name (str): + Name of the given property. For example, + "Screen-Resolution" for a TV product. Maximum + string size is 256 characters. + + This field is a member of `oneof`_ ``_property_name``. + text_value (MutableSequence[str]): + Text value of the given property. For + example, "8K(UHD)" could be a text value for a + TV product. Maximum repeatedness of this value + is 400. Values are stored in an arbitrary but + consistent order. Maximum string size is 256 + characters. + bool_value (bool): + Boolean value of the given property. For + example for a TV product, "True" or "False" if + the screen is UHD. + + This field is a member of `oneof`_ ``_bool_value``. + int_value (MutableSequence[int]): + Integer values of the given property. For + example, 1080 for a TV product's Screen + Resolution. Maximum repeatedness of this value + is 400. Values are stored in an arbitrary but + consistent order. + float_value (MutableSequence[float]): + Float values of the given property. For + example for a TV product 1.2345. Maximum + repeatedness of this value is 400. Values are + stored in an arbitrary but consistent order. + min_value (float): + Minimum float value of the given property. + For example for a TV product 1.00. + + This field is a member of `oneof`_ ``_min_value``. + max_value (float): + Maximum float value of the given property. + For example for a TV product 100.00. + + This field is a member of `oneof`_ ``_max_value``. + unit_code (str): + Unit of the given property. For example, + "Pixels" for a TV product. Maximum string size + is 256B. + + This field is a member of `oneof`_ ``_unit_code``. + """ + + property_name: str = proto.Field( + proto.STRING, + number=1, + optional=True, + ) + text_value: MutableSequence[str] = proto.RepeatedField( + proto.STRING, + number=2, + ) + bool_value: bool = proto.Field( + proto.BOOL, + number=3, + optional=True, + ) + int_value: MutableSequence[int] = proto.RepeatedField( + proto.INT64, + number=4, + ) + float_value: MutableSequence[float] = proto.RepeatedField( + proto.FLOAT, + number=5, + ) + min_value: float = proto.Field( + proto.FLOAT, + number=6, + optional=True, + ) + max_value: float = proto.Field( + proto.FLOAT, + number=7, + optional=True, + ) + unit_code: str = proto.Field( + proto.STRING, + number=8, + optional=True, + ) + + +class ProductSustainabilityIncentive(proto.Message): + r"""Information regarding sustainability-related incentive + programs such as rebates or tax relief. + + This message has `oneof`_ fields (mutually exclusive fields). + For each oneof, at most one member field can be set at the same time. + Setting any member of the oneof automatically clears all other + members. + + .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields + + Attributes: + amount (google.shopping.type.types.Price): + The fixed amount of the incentive. + + This field is a member of `oneof`_ ``value``. + percentage (float): + The percentage of the sale price that the + incentive is applied to. + + This field is a member of `oneof`_ ``value``. + type_ (google.shopping.merchant_products_v1.types.ProductSustainabilityIncentive.Type): + Sustainability incentive program. + + This field is a member of `oneof`_ ``_type``. + """ + + class Type(proto.Enum): + r"""Types of supported sustainability incentive programs. + + Values: + TYPE_UNSPECIFIED (0): + Unspecified or unknown sustainability + incentive type. + EV_TAX_CREDIT (1): + Program offering tax liability reductions for + electric vehicles and, in some countries, + plug-in hybrids. These reductions can be based + on a specific amount or a percentage of the sale + price. + EV_PRICE_DISCOUNT (2): + A subsidy program, often called an + environmental bonus, provides a purchase grant + for electric vehicles and, in some countries, + plug-in hybrids. The grant amount may be a fixed + sum or a percentage of the sale price. + """ + TYPE_UNSPECIFIED = 0 + EV_TAX_CREDIT = 1 + EV_PRICE_DISCOUNT = 2 + + amount: types.Price = proto.Field( + proto.MESSAGE, + number=2, + oneof="value", + message=types.Price, + ) + percentage: float = proto.Field( + proto.DOUBLE, + number=3, + oneof="value", + ) + type_: Type = proto.Field( + proto.ENUM, + number=1, + optional=True, + enum=Type, + ) + + +class AutomatedDiscounts(proto.Message): + r"""Information regarding Automated Discounts. + + Attributes: + prior_price (google.shopping.type.types.Price): + The price prior to the application of the + first price reduction. Absent if the information + about the prior price of the product is not + available. + prior_price_progressive (google.shopping.type.types.Price): + The price prior to the application of + consecutive price reductions. Absent if the + information about the prior price of the product + is not available. + gad_price (google.shopping.type.types.Price): + The current sale price for products with a price optimized + using Google Automated Discounts (GAD). Absent if the + information about the GAD_price of the product is not + available. + """ + + prior_price: types.Price = proto.Field( + proto.MESSAGE, + number=1, + message=types.Price, + ) + prior_price_progressive: types.Price = proto.Field( + proto.MESSAGE, + number=2, + message=types.Price, + ) + gad_price: types.Price = proto.Field( + proto.MESSAGE, + number=3, + message=types.Price, + ) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1beta/gapic_version.py b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1beta/gapic_version.py index 9650dd3a514f..20a9cd975b02 100644 --- a/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1beta/gapic_version.py +++ b/packages/google-shopping-merchant-products/google/shopping/merchant_products_v1beta/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "0.2.6" # {x-release-please-version} +__version__ = "0.0.0" # {x-release-please-version} diff --git a/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_product_inputs_service_delete_product_input_async.py b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_product_inputs_service_delete_product_input_async.py new file mode 100644 index 000000000000..06b1ad4f0cb3 --- /dev/null +++ b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_product_inputs_service_delete_product_input_async.py @@ -0,0 +1,51 @@ +# -*- 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 DeleteProductInput +# 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-shopping-merchant-products + + +# [START merchantapi_v1_generated_ProductInputsService_DeleteProductInput_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.shopping import merchant_products_v1 + + +async def sample_delete_product_input(): + # Create a client + client = merchant_products_v1.ProductInputsServiceAsyncClient() + + # Initialize request argument(s) + request = merchant_products_v1.DeleteProductInputRequest( + name="name_value", + data_source="data_source_value", + ) + + # Make the request + await client.delete_product_input(request=request) + + +# [END merchantapi_v1_generated_ProductInputsService_DeleteProductInput_async] diff --git a/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_product_inputs_service_delete_product_input_sync.py b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_product_inputs_service_delete_product_input_sync.py new file mode 100644 index 000000000000..5d6a0ff4b1d4 --- /dev/null +++ b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_product_inputs_service_delete_product_input_sync.py @@ -0,0 +1,51 @@ +# -*- 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 DeleteProductInput +# 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-shopping-merchant-products + + +# [START merchantapi_v1_generated_ProductInputsService_DeleteProductInput_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.shopping import merchant_products_v1 + + +def sample_delete_product_input(): + # Create a client + client = merchant_products_v1.ProductInputsServiceClient() + + # Initialize request argument(s) + request = merchant_products_v1.DeleteProductInputRequest( + name="name_value", + data_source="data_source_value", + ) + + # Make the request + client.delete_product_input(request=request) + + +# [END merchantapi_v1_generated_ProductInputsService_DeleteProductInput_sync] diff --git a/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_product_inputs_service_insert_product_input_async.py b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_product_inputs_service_insert_product_input_async.py new file mode 100644 index 000000000000..dd97d9dfc5a5 --- /dev/null +++ b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_product_inputs_service_insert_product_input_async.py @@ -0,0 +1,59 @@ +# -*- 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 InsertProductInput +# 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-shopping-merchant-products + + +# [START merchantapi_v1_generated_ProductInputsService_InsertProductInput_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.shopping import merchant_products_v1 + + +async def sample_insert_product_input(): + # Create a client + client = merchant_products_v1.ProductInputsServiceAsyncClient() + + # Initialize request argument(s) + product_input = merchant_products_v1.ProductInput() + product_input.offer_id = "offer_id_value" + product_input.content_language = "content_language_value" + product_input.feed_label = "feed_label_value" + + request = merchant_products_v1.InsertProductInputRequest( + parent="parent_value", + product_input=product_input, + data_source="data_source_value", + ) + + # Make the request + response = await client.insert_product_input(request=request) + + # Handle the response + print(response) + +# [END merchantapi_v1_generated_ProductInputsService_InsertProductInput_async] diff --git a/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_product_inputs_service_insert_product_input_sync.py b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_product_inputs_service_insert_product_input_sync.py new file mode 100644 index 000000000000..55ef5037c3ea --- /dev/null +++ b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_product_inputs_service_insert_product_input_sync.py @@ -0,0 +1,59 @@ +# -*- 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 InsertProductInput +# 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-shopping-merchant-products + + +# [START merchantapi_v1_generated_ProductInputsService_InsertProductInput_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.shopping import merchant_products_v1 + + +def sample_insert_product_input(): + # Create a client + client = merchant_products_v1.ProductInputsServiceClient() + + # Initialize request argument(s) + product_input = merchant_products_v1.ProductInput() + product_input.offer_id = "offer_id_value" + product_input.content_language = "content_language_value" + product_input.feed_label = "feed_label_value" + + request = merchant_products_v1.InsertProductInputRequest( + parent="parent_value", + product_input=product_input, + data_source="data_source_value", + ) + + # Make the request + response = client.insert_product_input(request=request) + + # Handle the response + print(response) + +# [END merchantapi_v1_generated_ProductInputsService_InsertProductInput_sync] diff --git a/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_product_inputs_service_update_product_input_async.py b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_product_inputs_service_update_product_input_async.py new file mode 100644 index 000000000000..5f88aef90bed --- /dev/null +++ b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_product_inputs_service_update_product_input_async.py @@ -0,0 +1,58 @@ +# -*- 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 UpdateProductInput +# 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-shopping-merchant-products + + +# [START merchantapi_v1_generated_ProductInputsService_UpdateProductInput_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.shopping import merchant_products_v1 + + +async def sample_update_product_input(): + # Create a client + client = merchant_products_v1.ProductInputsServiceAsyncClient() + + # Initialize request argument(s) + product_input = merchant_products_v1.ProductInput() + product_input.offer_id = "offer_id_value" + product_input.content_language = "content_language_value" + product_input.feed_label = "feed_label_value" + + request = merchant_products_v1.UpdateProductInputRequest( + product_input=product_input, + data_source="data_source_value", + ) + + # Make the request + response = await client.update_product_input(request=request) + + # Handle the response + print(response) + +# [END merchantapi_v1_generated_ProductInputsService_UpdateProductInput_async] diff --git a/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_product_inputs_service_update_product_input_sync.py b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_product_inputs_service_update_product_input_sync.py new file mode 100644 index 000000000000..bd005be7bbff --- /dev/null +++ b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_product_inputs_service_update_product_input_sync.py @@ -0,0 +1,58 @@ +# -*- 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 UpdateProductInput +# 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-shopping-merchant-products + + +# [START merchantapi_v1_generated_ProductInputsService_UpdateProductInput_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.shopping import merchant_products_v1 + + +def sample_update_product_input(): + # Create a client + client = merchant_products_v1.ProductInputsServiceClient() + + # Initialize request argument(s) + product_input = merchant_products_v1.ProductInput() + product_input.offer_id = "offer_id_value" + product_input.content_language = "content_language_value" + product_input.feed_label = "feed_label_value" + + request = merchant_products_v1.UpdateProductInputRequest( + product_input=product_input, + data_source="data_source_value", + ) + + # Make the request + response = client.update_product_input(request=request) + + # Handle the response + print(response) + +# [END merchantapi_v1_generated_ProductInputsService_UpdateProductInput_sync] diff --git a/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_products_service_get_product_async.py b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_products_service_get_product_async.py new file mode 100644 index 000000000000..5a462caa41c2 --- /dev/null +++ b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_products_service_get_product_async.py @@ -0,0 +1,52 @@ +# -*- 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 GetProduct +# 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-shopping-merchant-products + + +# [START merchantapi_v1_generated_ProductsService_GetProduct_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.shopping import merchant_products_v1 + + +async def sample_get_product(): + # Create a client + client = merchant_products_v1.ProductsServiceAsyncClient() + + # Initialize request argument(s) + request = merchant_products_v1.GetProductRequest( + name="name_value", + ) + + # Make the request + response = await client.get_product(request=request) + + # Handle the response + print(response) + +# [END merchantapi_v1_generated_ProductsService_GetProduct_async] diff --git a/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_products_service_get_product_sync.py b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_products_service_get_product_sync.py new file mode 100644 index 000000000000..4e09f3572fe4 --- /dev/null +++ b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_products_service_get_product_sync.py @@ -0,0 +1,52 @@ +# -*- 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 GetProduct +# 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-shopping-merchant-products + + +# [START merchantapi_v1_generated_ProductsService_GetProduct_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.shopping import merchant_products_v1 + + +def sample_get_product(): + # Create a client + client = merchant_products_v1.ProductsServiceClient() + + # Initialize request argument(s) + request = merchant_products_v1.GetProductRequest( + name="name_value", + ) + + # Make the request + response = client.get_product(request=request) + + # Handle the response + print(response) + +# [END merchantapi_v1_generated_ProductsService_GetProduct_sync] diff --git a/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_products_service_list_products_async.py b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_products_service_list_products_async.py new file mode 100644 index 000000000000..dd588cde394b --- /dev/null +++ b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_products_service_list_products_async.py @@ -0,0 +1,53 @@ +# -*- 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 ListProducts +# 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-shopping-merchant-products + + +# [START merchantapi_v1_generated_ProductsService_ListProducts_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.shopping import merchant_products_v1 + + +async def sample_list_products(): + # Create a client + client = merchant_products_v1.ProductsServiceAsyncClient() + + # Initialize request argument(s) + request = merchant_products_v1.ListProductsRequest( + parent="parent_value", + ) + + # Make the request + page_result = client.list_products(request=request) + + # Handle the response + async for response in page_result: + print(response) + +# [END merchantapi_v1_generated_ProductsService_ListProducts_async] diff --git a/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_products_service_list_products_sync.py b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_products_service_list_products_sync.py new file mode 100644 index 000000000000..3c8fbda19048 --- /dev/null +++ b/packages/google-shopping-merchant-products/samples/generated_samples/merchantapi_v1_generated_products_service_list_products_sync.py @@ -0,0 +1,53 @@ +# -*- 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 ListProducts +# 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-shopping-merchant-products + + +# [START merchantapi_v1_generated_ProductsService_ListProducts_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.shopping import merchant_products_v1 + + +def sample_list_products(): + # Create a client + client = merchant_products_v1.ProductsServiceClient() + + # Initialize request argument(s) + request = merchant_products_v1.ListProductsRequest( + parent="parent_value", + ) + + # Make the request + page_result = client.list_products(request=request) + + # Handle the response + for response in page_result: + print(response) + +# [END merchantapi_v1_generated_ProductsService_ListProducts_sync] diff --git a/packages/google-shopping-merchant-products/samples/generated_samples/snippet_metadata_google.shopping.merchant.products.v1.json b/packages/google-shopping-merchant-products/samples/generated_samples/snippet_metadata_google.shopping.merchant.products.v1.json new file mode 100644 index 000000000000..d4eccf58dc52 --- /dev/null +++ b/packages/google-shopping-merchant-products/samples/generated_samples/snippet_metadata_google.shopping.merchant.products.v1.json @@ -0,0 +1,814 @@ +{ + "clientLibrary": { + "apis": [ + { + "id": "google.shopping.merchant.products.v1", + "version": "v1" + } + ], + "language": "PYTHON", + "name": "google-shopping-merchant-products", + "version": "0.1.0" + }, + "snippets": [ + { + "canonical": true, + "clientMethod": { + "async": true, + "client": { + "fullName": "google.shopping.merchant_products_v1.ProductInputsServiceAsyncClient", + "shortName": "ProductInputsServiceAsyncClient" + }, + "fullName": "google.shopping.merchant_products_v1.ProductInputsServiceAsyncClient.delete_product_input", + "method": { + "fullName": "google.shopping.merchant.products.v1.ProductInputsService.DeleteProductInput", + "service": { + "fullName": "google.shopping.merchant.products.v1.ProductInputsService", + "shortName": "ProductInputsService" + }, + "shortName": "DeleteProductInput" + }, + "parameters": [ + { + "name": "request", + "type": "google.shopping.merchant_products_v1.types.DeleteProductInputRequest" + }, + { + "name": "name", + "type": "str" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "shortName": "delete_product_input" + }, + "description": "Sample for DeleteProductInput", + "file": "merchantapi_v1_generated_product_inputs_service_delete_product_input_async.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "merchantapi_v1_generated_ProductInputsService_DeleteProductInput_async", + "segments": [ + { + "end": 50, + "start": 27, + "type": "FULL" + }, + { + "end": 50, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 46, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "start": 47, + "type": "REQUEST_EXECUTION" + }, + { + "end": 51, + "type": "RESPONSE_HANDLING" + } + ], + "title": "merchantapi_v1_generated_product_inputs_service_delete_product_input_async.py" + }, + { + "canonical": true, + "clientMethod": { + "client": { + "fullName": "google.shopping.merchant_products_v1.ProductInputsServiceClient", + "shortName": "ProductInputsServiceClient" + }, + "fullName": "google.shopping.merchant_products_v1.ProductInputsServiceClient.delete_product_input", + "method": { + "fullName": "google.shopping.merchant.products.v1.ProductInputsService.DeleteProductInput", + "service": { + "fullName": "google.shopping.merchant.products.v1.ProductInputsService", + "shortName": "ProductInputsService" + }, + "shortName": "DeleteProductInput" + }, + "parameters": [ + { + "name": "request", + "type": "google.shopping.merchant_products_v1.types.DeleteProductInputRequest" + }, + { + "name": "name", + "type": "str" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "shortName": "delete_product_input" + }, + "description": "Sample for DeleteProductInput", + "file": "merchantapi_v1_generated_product_inputs_service_delete_product_input_sync.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "merchantapi_v1_generated_ProductInputsService_DeleteProductInput_sync", + "segments": [ + { + "end": 50, + "start": 27, + "type": "FULL" + }, + { + "end": 50, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 46, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "start": 47, + "type": "REQUEST_EXECUTION" + }, + { + "end": 51, + "type": "RESPONSE_HANDLING" + } + ], + "title": "merchantapi_v1_generated_product_inputs_service_delete_product_input_sync.py" + }, + { + "canonical": true, + "clientMethod": { + "async": true, + "client": { + "fullName": "google.shopping.merchant_products_v1.ProductInputsServiceAsyncClient", + "shortName": "ProductInputsServiceAsyncClient" + }, + "fullName": "google.shopping.merchant_products_v1.ProductInputsServiceAsyncClient.insert_product_input", + "method": { + "fullName": "google.shopping.merchant.products.v1.ProductInputsService.InsertProductInput", + "service": { + "fullName": "google.shopping.merchant.products.v1.ProductInputsService", + "shortName": "ProductInputsService" + }, + "shortName": "InsertProductInput" + }, + "parameters": [ + { + "name": "request", + "type": "google.shopping.merchant_products_v1.types.InsertProductInputRequest" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "resultType": "google.shopping.merchant_products_v1.types.ProductInput", + "shortName": "insert_product_input" + }, + "description": "Sample for InsertProductInput", + "file": "merchantapi_v1_generated_product_inputs_service_insert_product_input_async.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "merchantapi_v1_generated_ProductInputsService_InsertProductInput_async", + "segments": [ + { + "end": 58, + "start": 27, + "type": "FULL" + }, + { + "end": 58, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 52, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 55, + "start": 53, + "type": "REQUEST_EXECUTION" + }, + { + "end": 59, + "start": 56, + "type": "RESPONSE_HANDLING" + } + ], + "title": "merchantapi_v1_generated_product_inputs_service_insert_product_input_async.py" + }, + { + "canonical": true, + "clientMethod": { + "client": { + "fullName": "google.shopping.merchant_products_v1.ProductInputsServiceClient", + "shortName": "ProductInputsServiceClient" + }, + "fullName": "google.shopping.merchant_products_v1.ProductInputsServiceClient.insert_product_input", + "method": { + "fullName": "google.shopping.merchant.products.v1.ProductInputsService.InsertProductInput", + "service": { + "fullName": "google.shopping.merchant.products.v1.ProductInputsService", + "shortName": "ProductInputsService" + }, + "shortName": "InsertProductInput" + }, + "parameters": [ + { + "name": "request", + "type": "google.shopping.merchant_products_v1.types.InsertProductInputRequest" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "resultType": "google.shopping.merchant_products_v1.types.ProductInput", + "shortName": "insert_product_input" + }, + "description": "Sample for InsertProductInput", + "file": "merchantapi_v1_generated_product_inputs_service_insert_product_input_sync.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "merchantapi_v1_generated_ProductInputsService_InsertProductInput_sync", + "segments": [ + { + "end": 58, + "start": 27, + "type": "FULL" + }, + { + "end": 58, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 52, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 55, + "start": 53, + "type": "REQUEST_EXECUTION" + }, + { + "end": 59, + "start": 56, + "type": "RESPONSE_HANDLING" + } + ], + "title": "merchantapi_v1_generated_product_inputs_service_insert_product_input_sync.py" + }, + { + "canonical": true, + "clientMethod": { + "async": true, + "client": { + "fullName": "google.shopping.merchant_products_v1.ProductInputsServiceAsyncClient", + "shortName": "ProductInputsServiceAsyncClient" + }, + "fullName": "google.shopping.merchant_products_v1.ProductInputsServiceAsyncClient.update_product_input", + "method": { + "fullName": "google.shopping.merchant.products.v1.ProductInputsService.UpdateProductInput", + "service": { + "fullName": "google.shopping.merchant.products.v1.ProductInputsService", + "shortName": "ProductInputsService" + }, + "shortName": "UpdateProductInput" + }, + "parameters": [ + { + "name": "request", + "type": "google.shopping.merchant_products_v1.types.UpdateProductInputRequest" + }, + { + "name": "product_input", + "type": "google.shopping.merchant_products_v1.types.ProductInput" + }, + { + "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.shopping.merchant_products_v1.types.ProductInput", + "shortName": "update_product_input" + }, + "description": "Sample for UpdateProductInput", + "file": "merchantapi_v1_generated_product_inputs_service_update_product_input_async.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "merchantapi_v1_generated_ProductInputsService_UpdateProductInput_async", + "segments": [ + { + "end": 57, + "start": 27, + "type": "FULL" + }, + { + "end": 57, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 51, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 54, + "start": 52, + "type": "REQUEST_EXECUTION" + }, + { + "end": 58, + "start": 55, + "type": "RESPONSE_HANDLING" + } + ], + "title": "merchantapi_v1_generated_product_inputs_service_update_product_input_async.py" + }, + { + "canonical": true, + "clientMethod": { + "client": { + "fullName": "google.shopping.merchant_products_v1.ProductInputsServiceClient", + "shortName": "ProductInputsServiceClient" + }, + "fullName": "google.shopping.merchant_products_v1.ProductInputsServiceClient.update_product_input", + "method": { + "fullName": "google.shopping.merchant.products.v1.ProductInputsService.UpdateProductInput", + "service": { + "fullName": "google.shopping.merchant.products.v1.ProductInputsService", + "shortName": "ProductInputsService" + }, + "shortName": "UpdateProductInput" + }, + "parameters": [ + { + "name": "request", + "type": "google.shopping.merchant_products_v1.types.UpdateProductInputRequest" + }, + { + "name": "product_input", + "type": "google.shopping.merchant_products_v1.types.ProductInput" + }, + { + "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.shopping.merchant_products_v1.types.ProductInput", + "shortName": "update_product_input" + }, + "description": "Sample for UpdateProductInput", + "file": "merchantapi_v1_generated_product_inputs_service_update_product_input_sync.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "merchantapi_v1_generated_ProductInputsService_UpdateProductInput_sync", + "segments": [ + { + "end": 57, + "start": 27, + "type": "FULL" + }, + { + "end": 57, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 51, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 54, + "start": 52, + "type": "REQUEST_EXECUTION" + }, + { + "end": 58, + "start": 55, + "type": "RESPONSE_HANDLING" + } + ], + "title": "merchantapi_v1_generated_product_inputs_service_update_product_input_sync.py" + }, + { + "canonical": true, + "clientMethod": { + "async": true, + "client": { + "fullName": "google.shopping.merchant_products_v1.ProductsServiceAsyncClient", + "shortName": "ProductsServiceAsyncClient" + }, + "fullName": "google.shopping.merchant_products_v1.ProductsServiceAsyncClient.get_product", + "method": { + "fullName": "google.shopping.merchant.products.v1.ProductsService.GetProduct", + "service": { + "fullName": "google.shopping.merchant.products.v1.ProductsService", + "shortName": "ProductsService" + }, + "shortName": "GetProduct" + }, + "parameters": [ + { + "name": "request", + "type": "google.shopping.merchant_products_v1.types.GetProductRequest" + }, + { + "name": "name", + "type": "str" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "resultType": "google.shopping.merchant_products_v1.types.Product", + "shortName": "get_product" + }, + "description": "Sample for GetProduct", + "file": "merchantapi_v1_generated_products_service_get_product_async.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "merchantapi_v1_generated_ProductsService_GetProduct_async", + "segments": [ + { + "end": 51, + "start": 27, + "type": "FULL" + }, + { + "end": 51, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 45, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 48, + "start": 46, + "type": "REQUEST_EXECUTION" + }, + { + "end": 52, + "start": 49, + "type": "RESPONSE_HANDLING" + } + ], + "title": "merchantapi_v1_generated_products_service_get_product_async.py" + }, + { + "canonical": true, + "clientMethod": { + "client": { + "fullName": "google.shopping.merchant_products_v1.ProductsServiceClient", + "shortName": "ProductsServiceClient" + }, + "fullName": "google.shopping.merchant_products_v1.ProductsServiceClient.get_product", + "method": { + "fullName": "google.shopping.merchant.products.v1.ProductsService.GetProduct", + "service": { + "fullName": "google.shopping.merchant.products.v1.ProductsService", + "shortName": "ProductsService" + }, + "shortName": "GetProduct" + }, + "parameters": [ + { + "name": "request", + "type": "google.shopping.merchant_products_v1.types.GetProductRequest" + }, + { + "name": "name", + "type": "str" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "resultType": "google.shopping.merchant_products_v1.types.Product", + "shortName": "get_product" + }, + "description": "Sample for GetProduct", + "file": "merchantapi_v1_generated_products_service_get_product_sync.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "merchantapi_v1_generated_ProductsService_GetProduct_sync", + "segments": [ + { + "end": 51, + "start": 27, + "type": "FULL" + }, + { + "end": 51, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 45, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 48, + "start": 46, + "type": "REQUEST_EXECUTION" + }, + { + "end": 52, + "start": 49, + "type": "RESPONSE_HANDLING" + } + ], + "title": "merchantapi_v1_generated_products_service_get_product_sync.py" + }, + { + "canonical": true, + "clientMethod": { + "async": true, + "client": { + "fullName": "google.shopping.merchant_products_v1.ProductsServiceAsyncClient", + "shortName": "ProductsServiceAsyncClient" + }, + "fullName": "google.shopping.merchant_products_v1.ProductsServiceAsyncClient.list_products", + "method": { + "fullName": "google.shopping.merchant.products.v1.ProductsService.ListProducts", + "service": { + "fullName": "google.shopping.merchant.products.v1.ProductsService", + "shortName": "ProductsService" + }, + "shortName": "ListProducts" + }, + "parameters": [ + { + "name": "request", + "type": "google.shopping.merchant_products_v1.types.ListProductsRequest" + }, + { + "name": "parent", + "type": "str" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "resultType": "google.shopping.merchant_products_v1.services.products_service.pagers.ListProductsAsyncPager", + "shortName": "list_products" + }, + "description": "Sample for ListProducts", + "file": "merchantapi_v1_generated_products_service_list_products_async.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "merchantapi_v1_generated_ProductsService_ListProducts_async", + "segments": [ + { + "end": 52, + "start": 27, + "type": "FULL" + }, + { + "end": 52, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 45, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 48, + "start": 46, + "type": "REQUEST_EXECUTION" + }, + { + "end": 53, + "start": 49, + "type": "RESPONSE_HANDLING" + } + ], + "title": "merchantapi_v1_generated_products_service_list_products_async.py" + }, + { + "canonical": true, + "clientMethod": { + "client": { + "fullName": "google.shopping.merchant_products_v1.ProductsServiceClient", + "shortName": "ProductsServiceClient" + }, + "fullName": "google.shopping.merchant_products_v1.ProductsServiceClient.list_products", + "method": { + "fullName": "google.shopping.merchant.products.v1.ProductsService.ListProducts", + "service": { + "fullName": "google.shopping.merchant.products.v1.ProductsService", + "shortName": "ProductsService" + }, + "shortName": "ListProducts" + }, + "parameters": [ + { + "name": "request", + "type": "google.shopping.merchant_products_v1.types.ListProductsRequest" + }, + { + "name": "parent", + "type": "str" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "resultType": "google.shopping.merchant_products_v1.services.products_service.pagers.ListProductsPager", + "shortName": "list_products" + }, + "description": "Sample for ListProducts", + "file": "merchantapi_v1_generated_products_service_list_products_sync.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "merchantapi_v1_generated_ProductsService_ListProducts_sync", + "segments": [ + { + "end": 52, + "start": 27, + "type": "FULL" + }, + { + "end": 52, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 45, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 48, + "start": 46, + "type": "REQUEST_EXECUTION" + }, + { + "end": 53, + "start": 49, + "type": "RESPONSE_HANDLING" + } + ], + "title": "merchantapi_v1_generated_products_service_list_products_sync.py" + } + ] +} diff --git a/packages/google-shopping-merchant-products/samples/generated_samples/snippet_metadata_google.shopping.merchant.products.v1beta.json b/packages/google-shopping-merchant-products/samples/generated_samples/snippet_metadata_google.shopping.merchant.products.v1beta.json index 4f9524ef1ed4..efc245a8341b 100644 --- a/packages/google-shopping-merchant-products/samples/generated_samples/snippet_metadata_google.shopping.merchant.products.v1beta.json +++ b/packages/google-shopping-merchant-products/samples/generated_samples/snippet_metadata_google.shopping.merchant.products.v1beta.json @@ -8,7 +8,7 @@ ], "language": "PYTHON", "name": "google-shopping-merchant-products", - "version": "0.2.6" + "version": "0.1.0" }, "snippets": [ { diff --git a/packages/google-shopping-merchant-products/scripts/fixup_merchant_products_v1_keywords.py b/packages/google-shopping-merchant-products/scripts/fixup_merchant_products_v1_keywords.py new file mode 100644 index 000000000000..da63187314ee --- /dev/null +++ b/packages/google-shopping-merchant-products/scripts/fixup_merchant_products_v1_keywords.py @@ -0,0 +1,180 @@ +#! /usr/bin/env python3 +# -*- 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. +# +import argparse +import os +import libcst as cst +import pathlib +import sys +from typing import (Any, Callable, Dict, List, Sequence, Tuple) + + +def partition( + predicate: Callable[[Any], bool], + iterator: Sequence[Any] +) -> Tuple[List[Any], List[Any]]: + """A stable, out-of-place partition.""" + results = ([], []) + + for i in iterator: + results[int(predicate(i))].append(i) + + # Returns trueList, falseList + return results[1], results[0] + + +class merchant_productsCallTransformer(cst.CSTTransformer): + CTRL_PARAMS: Tuple[str] = ('retry', 'timeout', 'metadata') + METHOD_TO_PARAMS: Dict[str, Tuple[str]] = { + 'delete_product_input': ('name', 'data_source', ), + 'get_product': ('name', ), + 'insert_product_input': ('parent', 'product_input', 'data_source', ), + 'list_products': ('parent', 'page_size', 'page_token', ), + 'update_product_input': ('product_input', 'data_source', 'update_mask', ), + } + + def leave_Call(self, original: cst.Call, updated: cst.Call) -> cst.CSTNode: + try: + key = original.func.attr.value + kword_params = self.METHOD_TO_PARAMS[key] + except (AttributeError, KeyError): + # Either not a method from the API or too convoluted to be sure. + return updated + + # If the existing code is valid, keyword args come after positional args. + # Therefore, all positional args must map to the first parameters. + args, kwargs = partition(lambda a: not bool(a.keyword), updated.args) + if any(k.keyword.value == "request" for k in kwargs): + # We've already fixed this file, don't fix it again. + return updated + + kwargs, ctrl_kwargs = partition( + lambda a: a.keyword.value not in self.CTRL_PARAMS, + kwargs + ) + + args, ctrl_args = args[:len(kword_params)], args[len(kword_params):] + ctrl_kwargs.extend(cst.Arg(value=a.value, keyword=cst.Name(value=ctrl)) + for a, ctrl in zip(ctrl_args, self.CTRL_PARAMS)) + + request_arg = cst.Arg( + value=cst.Dict([ + cst.DictElement( + cst.SimpleString("'{}'".format(name)), +cst.Element(value=arg.value) + ) + # Note: the args + kwargs looks silly, but keep in mind that + # the control parameters had to be stripped out, and that + # those could have been passed positionally or by keyword. + for name, arg in zip(kword_params, args + kwargs)]), + keyword=cst.Name("request") + ) + + return updated.with_changes( + args=[request_arg] + ctrl_kwargs + ) + + +def fix_files( + in_dir: pathlib.Path, + out_dir: pathlib.Path, + *, + transformer=merchant_productsCallTransformer(), +): + """Duplicate the input dir to the output dir, fixing file method calls. + + Preconditions: + * in_dir is a real directory + * out_dir is a real, empty directory + """ + pyfile_gen = ( + pathlib.Path(os.path.join(root, f)) + for root, _, files in os.walk(in_dir) + for f in files if os.path.splitext(f)[1] == ".py" + ) + + for fpath in pyfile_gen: + with open(fpath, 'r') as f: + src = f.read() + + # Parse the code and insert method call fixes. + tree = cst.parse_module(src) + updated = tree.visit(transformer) + + # Create the path and directory structure for the new file. + updated_path = out_dir.joinpath(fpath.relative_to(in_dir)) + updated_path.parent.mkdir(parents=True, exist_ok=True) + + # Generate the updated source file at the corresponding path. + with open(updated_path, 'w') as f: + f.write(updated.code) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description="""Fix up source that uses the merchant_products client library. + +The existing sources are NOT overwritten but are copied to output_dir with changes made. + +Note: This tool operates at a best-effort level at converting positional + parameters in client method calls to keyword based parameters. + Cases where it WILL FAIL include + A) * or ** expansion in a method call. + B) Calls via function or method alias (includes free function calls) + C) Indirect or dispatched calls (e.g. the method is looked up dynamically) + + These all constitute false negatives. The tool will also detect false + positives when an API method shares a name with another method. +""") + parser.add_argument( + '-d', + '--input-directory', + required=True, + dest='input_dir', + help='the input directory to walk for python files to fix up', + ) + parser.add_argument( + '-o', + '--output-directory', + required=True, + dest='output_dir', + help='the directory to output files fixed via un-flattening', + ) + args = parser.parse_args() + input_dir = pathlib.Path(args.input_dir) + output_dir = pathlib.Path(args.output_dir) + if not input_dir.is_dir(): + print( + f"input directory '{input_dir}' does not exist or is not a directory", + file=sys.stderr, + ) + sys.exit(-1) + + if not output_dir.is_dir(): + print( + f"output directory '{output_dir}' does not exist or is not a directory", + file=sys.stderr, + ) + sys.exit(-1) + + if os.listdir(output_dir): + print( + f"output directory '{output_dir}' is not empty", + file=sys.stderr, + ) + sys.exit(-1) + + fix_files(input_dir, output_dir) diff --git a/packages/google-shopping-merchant-products/tests/unit/gapic/merchant_products_v1/__init__.py b/packages/google-shopping-merchant-products/tests/unit/gapic/merchant_products_v1/__init__.py new file mode 100644 index 000000000000..cbf94b283c70 --- /dev/null +++ b/packages/google-shopping-merchant-products/tests/unit/gapic/merchant_products_v1/__init__.py @@ -0,0 +1,15 @@ +# -*- 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. +# diff --git a/packages/google-shopping-merchant-products/tests/unit/gapic/merchant_products_v1/test_product_inputs_service.py b/packages/google-shopping-merchant-products/tests/unit/gapic/merchant_products_v1/test_product_inputs_service.py new file mode 100644 index 000000000000..f124fcb45f65 --- /dev/null +++ b/packages/google-shopping-merchant-products/tests/unit/gapic/merchant_products_v1/test_product_inputs_service.py @@ -0,0 +1,4679 @@ +# -*- 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. +# +import os + +# try/except added for compatibility with python < 3.8 +try: + from unittest import mock + from unittest.mock import AsyncMock # pragma: NO COVER +except ImportError: # pragma: NO COVER + import mock + +from collections.abc import AsyncIterable, Iterable +import json +import math + +from google.api_core import api_core_version +from google.protobuf import json_format +import grpc +from grpc.experimental import aio +from proto.marshal.rules import wrappers +from proto.marshal.rules.dates import DurationRule, TimestampRule +import pytest +from requests import PreparedRequest, Request, Response +from requests.sessions import Session + +try: + from google.auth.aio import credentials as ga_credentials_async + + HAS_GOOGLE_AUTH_AIO = True +except ImportError: # pragma: NO COVER + HAS_GOOGLE_AUTH_AIO = False + +from google.api_core import gapic_v1, grpc_helpers, grpc_helpers_async, path_template +from google.api_core import client_options +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +import google.auth +from google.auth import credentials as ga_credentials +from google.auth.exceptions import MutualTLSChannelError +from google.oauth2 import service_account +from google.protobuf import field_mask_pb2 # type: ignore +from google.protobuf import timestamp_pb2 # type: ignore +from google.shopping.type.types import types +from google.type import interval_pb2 # type: ignore + +from google.shopping.merchant_products_v1.services.product_inputs_service import ( + ProductInputsServiceAsyncClient, + ProductInputsServiceClient, + transports, +) +from google.shopping.merchant_products_v1.types import productinputs, products_common + +CRED_INFO_JSON = { + "credential_source": "/path/to/file", + "credential_type": "service account credentials", + "principal": "service-account@example.com", +} +CRED_INFO_STRING = json.dumps(CRED_INFO_JSON) + + +async def mock_async_gen(data, chunk_size=1): + for i in range(0, len(data)): # pragma: NO COVER + chunk = data[i : i + chunk_size] + yield chunk.encode("utf-8") + + +def client_cert_source_callback(): + return b"cert bytes", b"key bytes" + + +# TODO: use async auth anon credentials by default once the minimum version of google-auth is upgraded. +# See related issue: https://github.com/googleapis/gapic-generator-python/issues/2107. +def async_anonymous_credentials(): + if HAS_GOOGLE_AUTH_AIO: + return ga_credentials_async.AnonymousCredentials() + return ga_credentials.AnonymousCredentials() + + +# If default endpoint is localhost, then default mtls endpoint will be the same. +# This method modifies the default endpoint so the client can produce a different +# mtls endpoint for endpoint testing purposes. +def modify_default_endpoint(client): + return ( + "foo.googleapis.com" + if ("localhost" in client.DEFAULT_ENDPOINT) + else client.DEFAULT_ENDPOINT + ) + + +# If default endpoint template is localhost, then default mtls endpoint will be the same. +# This method modifies the default endpoint template so the client can produce a different +# mtls endpoint for endpoint testing purposes. +def modify_default_endpoint_template(client): + return ( + "test.{UNIVERSE_DOMAIN}" + if ("localhost" in client._DEFAULT_ENDPOINT_TEMPLATE) + else client._DEFAULT_ENDPOINT_TEMPLATE + ) + + +def test__get_default_mtls_endpoint(): + api_endpoint = "example.googleapis.com" + api_mtls_endpoint = "example.mtls.googleapis.com" + sandbox_endpoint = "example.sandbox.googleapis.com" + sandbox_mtls_endpoint = "example.mtls.sandbox.googleapis.com" + non_googleapi = "api.example.com" + + assert ProductInputsServiceClient._get_default_mtls_endpoint(None) is None + assert ( + ProductInputsServiceClient._get_default_mtls_endpoint(api_endpoint) + == api_mtls_endpoint + ) + assert ( + ProductInputsServiceClient._get_default_mtls_endpoint(api_mtls_endpoint) + == api_mtls_endpoint + ) + assert ( + ProductInputsServiceClient._get_default_mtls_endpoint(sandbox_endpoint) + == sandbox_mtls_endpoint + ) + assert ( + ProductInputsServiceClient._get_default_mtls_endpoint(sandbox_mtls_endpoint) + == sandbox_mtls_endpoint + ) + assert ( + ProductInputsServiceClient._get_default_mtls_endpoint(non_googleapi) + == non_googleapi + ) + + +def test__read_environment_variables(): + assert ProductInputsServiceClient._read_environment_variables() == ( + False, + "auto", + None, + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + assert ProductInputsServiceClient._read_environment_variables() == ( + True, + "auto", + None, + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): + assert ProductInputsServiceClient._read_environment_variables() == ( + False, + "auto", + None, + ) + + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError) as excinfo: + ProductInputsServiceClient._read_environment_variables() + assert ( + str(excinfo.value) + == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + assert ProductInputsServiceClient._read_environment_variables() == ( + False, + "never", + None, + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + assert ProductInputsServiceClient._read_environment_variables() == ( + False, + "always", + None, + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}): + assert ProductInputsServiceClient._read_environment_variables() == ( + False, + "auto", + None, + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError) as excinfo: + ProductInputsServiceClient._read_environment_variables() + assert ( + str(excinfo.value) + == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" + ) + + with mock.patch.dict(os.environ, {"GOOGLE_CLOUD_UNIVERSE_DOMAIN": "foo.com"}): + assert ProductInputsServiceClient._read_environment_variables() == ( + False, + "auto", + "foo.com", + ) + + +def test__get_client_cert_source(): + mock_provided_cert_source = mock.Mock() + mock_default_cert_source = mock.Mock() + + assert ProductInputsServiceClient._get_client_cert_source(None, False) is None + assert ( + ProductInputsServiceClient._get_client_cert_source( + mock_provided_cert_source, False + ) + is None + ) + assert ( + ProductInputsServiceClient._get_client_cert_source( + mock_provided_cert_source, True + ) + == mock_provided_cert_source + ) + + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", return_value=True + ): + with mock.patch( + "google.auth.transport.mtls.default_client_cert_source", + return_value=mock_default_cert_source, + ): + assert ( + ProductInputsServiceClient._get_client_cert_source(None, True) + is mock_default_cert_source + ) + assert ( + ProductInputsServiceClient._get_client_cert_source( + mock_provided_cert_source, "true" + ) + is mock_provided_cert_source + ) + + +@mock.patch.object( + ProductInputsServiceClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(ProductInputsServiceClient), +) +@mock.patch.object( + ProductInputsServiceAsyncClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(ProductInputsServiceAsyncClient), +) +def test__get_api_endpoint(): + api_override = "foo.com" + mock_client_cert_source = mock.Mock() + default_universe = ProductInputsServiceClient._DEFAULT_UNIVERSE + default_endpoint = ProductInputsServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=default_universe + ) + mock_universe = "bar.com" + mock_endpoint = ProductInputsServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=mock_universe + ) + + assert ( + ProductInputsServiceClient._get_api_endpoint( + api_override, mock_client_cert_source, default_universe, "always" + ) + == api_override + ) + assert ( + ProductInputsServiceClient._get_api_endpoint( + None, mock_client_cert_source, default_universe, "auto" + ) + == ProductInputsServiceClient.DEFAULT_MTLS_ENDPOINT + ) + assert ( + ProductInputsServiceClient._get_api_endpoint( + None, None, default_universe, "auto" + ) + == default_endpoint + ) + assert ( + ProductInputsServiceClient._get_api_endpoint( + None, None, default_universe, "always" + ) + == ProductInputsServiceClient.DEFAULT_MTLS_ENDPOINT + ) + assert ( + ProductInputsServiceClient._get_api_endpoint( + None, mock_client_cert_source, default_universe, "always" + ) + == ProductInputsServiceClient.DEFAULT_MTLS_ENDPOINT + ) + assert ( + ProductInputsServiceClient._get_api_endpoint(None, None, mock_universe, "never") + == mock_endpoint + ) + assert ( + ProductInputsServiceClient._get_api_endpoint( + None, None, default_universe, "never" + ) + == default_endpoint + ) + + with pytest.raises(MutualTLSChannelError) as excinfo: + ProductInputsServiceClient._get_api_endpoint( + None, mock_client_cert_source, mock_universe, "auto" + ) + assert ( + str(excinfo.value) + == "mTLS is not supported in any universe other than googleapis.com." + ) + + +def test__get_universe_domain(): + client_universe_domain = "foo.com" + universe_domain_env = "bar.com" + + assert ( + ProductInputsServiceClient._get_universe_domain( + client_universe_domain, universe_domain_env + ) + == client_universe_domain + ) + assert ( + ProductInputsServiceClient._get_universe_domain(None, universe_domain_env) + == universe_domain_env + ) + assert ( + ProductInputsServiceClient._get_universe_domain(None, None) + == ProductInputsServiceClient._DEFAULT_UNIVERSE + ) + + with pytest.raises(ValueError) as excinfo: + ProductInputsServiceClient._get_universe_domain("", None) + assert str(excinfo.value) == "Universe Domain cannot be an empty string." + + +@pytest.mark.parametrize( + "error_code,cred_info_json,show_cred_info", + [ + (401, CRED_INFO_JSON, True), + (403, CRED_INFO_JSON, True), + (404, CRED_INFO_JSON, True), + (500, CRED_INFO_JSON, False), + (401, None, False), + (403, None, False), + (404, None, False), + (500, None, False), + ], +) +def test__add_cred_info_for_auth_errors(error_code, cred_info_json, show_cred_info): + cred = mock.Mock(["get_cred_info"]) + cred.get_cred_info = mock.Mock(return_value=cred_info_json) + client = ProductInputsServiceClient(credentials=cred) + client._transport._credentials = cred + + error = core_exceptions.GoogleAPICallError("message", details=["foo"]) + error.code = error_code + + client._add_cred_info_for_auth_errors(error) + if show_cred_info: + assert error.details == ["foo", CRED_INFO_STRING] + else: + assert error.details == ["foo"] + + +@pytest.mark.parametrize("error_code", [401, 403, 404, 500]) +def test__add_cred_info_for_auth_errors_no_get_cred_info(error_code): + cred = mock.Mock([]) + assert not hasattr(cred, "get_cred_info") + client = ProductInputsServiceClient(credentials=cred) + client._transport._credentials = cred + + error = core_exceptions.GoogleAPICallError("message", details=[]) + error.code = error_code + + client._add_cred_info_for_auth_errors(error) + assert error.details == [] + + +@pytest.mark.parametrize( + "client_class,transport_name", + [ + (ProductInputsServiceClient, "grpc"), + (ProductInputsServiceAsyncClient, "grpc_asyncio"), + (ProductInputsServiceClient, "rest"), + ], +) +def test_product_inputs_service_client_from_service_account_info( + client_class, transport_name +): + creds = ga_credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_info" + ) as factory: + factory.return_value = creds + info = {"valid": True} + client = client_class.from_service_account_info(info, transport=transport_name) + assert client.transport._credentials == creds + assert isinstance(client, client_class) + + assert client.transport._host == ( + "merchantapi.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://merchantapi.googleapis.com" + ) + + +@pytest.mark.parametrize( + "transport_class,transport_name", + [ + (transports.ProductInputsServiceGrpcTransport, "grpc"), + (transports.ProductInputsServiceGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.ProductInputsServiceRestTransport, "rest"), + ], +) +def test_product_inputs_service_client_service_account_always_use_jwt( + transport_class, transport_name +): + with mock.patch.object( + service_account.Credentials, "with_always_use_jwt_access", create=True + ) as use_jwt: + creds = service_account.Credentials(None, None, None) + transport = transport_class(credentials=creds, always_use_jwt_access=True) + use_jwt.assert_called_once_with(True) + + with mock.patch.object( + service_account.Credentials, "with_always_use_jwt_access", create=True + ) as use_jwt: + creds = service_account.Credentials(None, None, None) + transport = transport_class(credentials=creds, always_use_jwt_access=False) + use_jwt.assert_not_called() + + +@pytest.mark.parametrize( + "client_class,transport_name", + [ + (ProductInputsServiceClient, "grpc"), + (ProductInputsServiceAsyncClient, "grpc_asyncio"), + (ProductInputsServiceClient, "rest"), + ], +) +def test_product_inputs_service_client_from_service_account_file( + client_class, transport_name +): + creds = ga_credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_file" + ) as factory: + factory.return_value = creds + client = client_class.from_service_account_file( + "dummy/file/path.json", transport=transport_name + ) + assert client.transport._credentials == creds + assert isinstance(client, client_class) + + client = client_class.from_service_account_json( + "dummy/file/path.json", transport=transport_name + ) + assert client.transport._credentials == creds + assert isinstance(client, client_class) + + assert client.transport._host == ( + "merchantapi.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://merchantapi.googleapis.com" + ) + + +def test_product_inputs_service_client_get_transport_class(): + transport = ProductInputsServiceClient.get_transport_class() + available_transports = [ + transports.ProductInputsServiceGrpcTransport, + transports.ProductInputsServiceRestTransport, + ] + assert transport in available_transports + + transport = ProductInputsServiceClient.get_transport_class("grpc") + assert transport == transports.ProductInputsServiceGrpcTransport + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + ( + ProductInputsServiceClient, + transports.ProductInputsServiceGrpcTransport, + "grpc", + ), + ( + ProductInputsServiceAsyncClient, + transports.ProductInputsServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ( + ProductInputsServiceClient, + transports.ProductInputsServiceRestTransport, + "rest", + ), + ], +) +@mock.patch.object( + ProductInputsServiceClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(ProductInputsServiceClient), +) +@mock.patch.object( + ProductInputsServiceAsyncClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(ProductInputsServiceAsyncClient), +) +def test_product_inputs_service_client_client_options( + client_class, transport_class, transport_name +): + # Check that if channel is provided we won't create a new one. + with mock.patch.object(ProductInputsServiceClient, "get_transport_class") as gtc: + transport = transport_class(credentials=ga_credentials.AnonymousCredentials()) + client = client_class(transport=transport) + gtc.assert_not_called() + + # Check that if channel is provided via str we will create a new one. + with mock.patch.object(ProductInputsServiceClient, "get_transport_class") as gtc: + client = client_class(transport=transport_name) + gtc.assert_called() + + # Check the case api_endpoint is provided. + options = client_options.ClientOptions(api_endpoint="squid.clam.whelk") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(transport=transport_name, client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host="squid.clam.whelk", + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is + # "never". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ), + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is + # "always". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_MTLS_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError) as excinfo: + client = client_class(transport=transport_name) + assert ( + str(excinfo.value) + == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" + ) + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError) as excinfo: + client = client_class(transport=transport_name) + assert ( + str(excinfo.value) + == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options, transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ), + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id="octopus", + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + # Check the case api_endpoint is provided + options = client_options.ClientOptions( + api_audience="https://language.googleapis.com" + ) + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options, transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ), + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience="https://language.googleapis.com", + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,use_client_cert_env", + [ + ( + ProductInputsServiceClient, + transports.ProductInputsServiceGrpcTransport, + "grpc", + "true", + ), + ( + ProductInputsServiceAsyncClient, + transports.ProductInputsServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + ( + ProductInputsServiceClient, + transports.ProductInputsServiceGrpcTransport, + "grpc", + "false", + ), + ( + ProductInputsServiceAsyncClient, + transports.ProductInputsServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ( + ProductInputsServiceClient, + transports.ProductInputsServiceRestTransport, + "rest", + "true", + ), + ( + ProductInputsServiceClient, + transports.ProductInputsServiceRestTransport, + "rest", + "false", + ), + ], +) +@mock.patch.object( + ProductInputsServiceClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(ProductInputsServiceClient), +) +@mock.patch.object( + ProductInputsServiceAsyncClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(ProductInputsServiceAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_product_inputs_service_client_mtls_env_auto( + client_class, transport_class, transport_name, use_client_cert_env +): + # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default + # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists. + + # Check the case client_cert_source is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + options = client_options.ClientOptions( + client_cert_source=client_cert_source_callback + ) + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options, transport=transport_name) + + if use_client_cert_env == "false": + expected_client_cert_source = None + expected_host = client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ) + else: + expected_client_cert_source = client_cert_source_callback + expected_host = client.DEFAULT_MTLS_ENDPOINT + + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + # Check the case ADC client cert is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, + ): + with mock.patch( + "google.auth.transport.mtls.default_client_cert_source", + return_value=client_cert_source_callback, + ): + if use_client_cert_env == "false": + expected_host = client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ) + expected_client_cert_source = None + else: + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_client_cert_source = client_cert_source_callback + + patched.return_value = None + client = client_class(transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + patched.return_value = None + client = client_class(transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ), + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + +@pytest.mark.parametrize( + "client_class", [ProductInputsServiceClient, ProductInputsServiceAsyncClient] +) +@mock.patch.object( + ProductInputsServiceClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(ProductInputsServiceClient), +) +@mock.patch.object( + ProductInputsServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(ProductInputsServiceAsyncClient), +) +def test_product_inputs_service_client_get_mtls_endpoint_and_cert_source(client_class): + mock_client_cert_source = mock.Mock() + + # Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "true". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + mock_api_endpoint = "foo" + options = client_options.ClientOptions( + client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint + ) + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source( + options + ) + assert api_endpoint == mock_api_endpoint + assert cert_source == mock_client_cert_source + + # Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "false". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): + mock_client_cert_source = mock.Mock() + mock_api_endpoint = "foo" + options = client_options.ClientOptions( + client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint + ) + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source( + options + ) + assert api_endpoint == mock_api_endpoint + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "never". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_ENDPOINT + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "always". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert doesn't exist. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_ENDPOINT + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert exists. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, + ): + with mock.patch( + "google.auth.transport.mtls.default_client_cert_source", + return_value=mock_client_cert_source, + ): + ( + api_endpoint, + cert_source, + ) = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT + assert cert_source == mock_client_cert_source + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError) as excinfo: + client_class.get_mtls_endpoint_and_cert_source() + + assert ( + str(excinfo.value) + == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" + ) + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError) as excinfo: + client_class.get_mtls_endpoint_and_cert_source() + + assert ( + str(excinfo.value) + == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) + + +@pytest.mark.parametrize( + "client_class", [ProductInputsServiceClient, ProductInputsServiceAsyncClient] +) +@mock.patch.object( + ProductInputsServiceClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(ProductInputsServiceClient), +) +@mock.patch.object( + ProductInputsServiceAsyncClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(ProductInputsServiceAsyncClient), +) +def test_product_inputs_service_client_client_api_endpoint(client_class): + mock_client_cert_source = client_cert_source_callback + api_override = "foo.com" + default_universe = ProductInputsServiceClient._DEFAULT_UNIVERSE + default_endpoint = ProductInputsServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=default_universe + ) + mock_universe = "bar.com" + mock_endpoint = ProductInputsServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=mock_universe + ) + + # If ClientOptions.api_endpoint is set and GOOGLE_API_USE_CLIENT_CERTIFICATE="true", + # use ClientOptions.api_endpoint as the api endpoint regardless. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ): + options = client_options.ClientOptions( + client_cert_source=mock_client_cert_source, api_endpoint=api_override + ) + client = client_class( + client_options=options, + credentials=ga_credentials.AnonymousCredentials(), + ) + assert client.api_endpoint == api_override + + # If ClientOptions.api_endpoint is not set and GOOGLE_API_USE_MTLS_ENDPOINT="never", + # use the _DEFAULT_ENDPOINT_TEMPLATE populated with GDU as the api endpoint. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + client = client_class(credentials=ga_credentials.AnonymousCredentials()) + assert client.api_endpoint == default_endpoint + + # If ClientOptions.api_endpoint is not set and GOOGLE_API_USE_MTLS_ENDPOINT="always", + # use the DEFAULT_MTLS_ENDPOINT as the api endpoint. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + client = client_class(credentials=ga_credentials.AnonymousCredentials()) + assert client.api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT + + # If ClientOptions.api_endpoint is not set, GOOGLE_API_USE_MTLS_ENDPOINT="auto" (default), + # GOOGLE_API_USE_CLIENT_CERTIFICATE="false" (default), default cert source doesn't exist, + # and ClientOptions.universe_domain="bar.com", + # use the _DEFAULT_ENDPOINT_TEMPLATE populated with universe domain as the api endpoint. + options = client_options.ClientOptions() + universe_exists = hasattr(options, "universe_domain") + if universe_exists: + options = client_options.ClientOptions(universe_domain=mock_universe) + client = client_class( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + else: + client = client_class( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + assert client.api_endpoint == ( + mock_endpoint if universe_exists else default_endpoint + ) + assert client.universe_domain == ( + mock_universe if universe_exists else default_universe + ) + + # If ClientOptions does not have a universe domain attribute and GOOGLE_API_USE_MTLS_ENDPOINT="never", + # use the _DEFAULT_ENDPOINT_TEMPLATE populated with GDU as the api endpoint. + options = client_options.ClientOptions() + if hasattr(options, "universe_domain"): + delattr(options, "universe_domain") + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + client = client_class( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + assert client.api_endpoint == default_endpoint + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + ( + ProductInputsServiceClient, + transports.ProductInputsServiceGrpcTransport, + "grpc", + ), + ( + ProductInputsServiceAsyncClient, + transports.ProductInputsServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ( + ProductInputsServiceClient, + transports.ProductInputsServiceRestTransport, + "rest", + ), + ], +) +def test_product_inputs_service_client_client_options_scopes( + client_class, transport_class, transport_name +): + # Check the case scopes are provided. + options = client_options.ClientOptions( + scopes=["1", "2"], + ) + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options, transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ), + scopes=["1", "2"], + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,grpc_helpers", + [ + ( + ProductInputsServiceClient, + transports.ProductInputsServiceGrpcTransport, + "grpc", + grpc_helpers, + ), + ( + ProductInputsServiceAsyncClient, + transports.ProductInputsServiceGrpcAsyncIOTransport, + "grpc_asyncio", + grpc_helpers_async, + ), + ( + ProductInputsServiceClient, + transports.ProductInputsServiceRestTransport, + "rest", + None, + ), + ], +) +def test_product_inputs_service_client_client_options_credentials_file( + client_class, transport_class, transport_name, grpc_helpers +): + # Check the case credentials file is provided. + options = client_options.ClientOptions(credentials_file="credentials.json") + + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options, transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file="credentials.json", + host=client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ), + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + +def test_product_inputs_service_client_client_options_from_dict(): + with mock.patch( + "google.shopping.merchant_products_v1.services.product_inputs_service.transports.ProductInputsServiceGrpcTransport.__init__" + ) as grpc_transport: + grpc_transport.return_value = None + client = ProductInputsServiceClient( + client_options={"api_endpoint": "squid.clam.whelk"} + ) + grpc_transport.assert_called_once_with( + credentials=None, + credentials_file=None, + host="squid.clam.whelk", + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,grpc_helpers", + [ + ( + ProductInputsServiceClient, + transports.ProductInputsServiceGrpcTransport, + "grpc", + grpc_helpers, + ), + ( + ProductInputsServiceAsyncClient, + transports.ProductInputsServiceGrpcAsyncIOTransport, + "grpc_asyncio", + grpc_helpers_async, + ), + ], +) +def test_product_inputs_service_client_create_channel_credentials_file( + client_class, transport_class, transport_name, grpc_helpers +): + # Check the case credentials file is provided. + options = client_options.ClientOptions(credentials_file="credentials.json") + + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options, transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file="credentials.json", + host=client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ), + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + # test that the credentials from file are saved and used as the credentials. + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch.object( + google.auth, "default", autospec=True + ) as adc, mock.patch.object( + grpc_helpers, "create_channel" + ) as create_channel: + creds = ga_credentials.AnonymousCredentials() + file_creds = ga_credentials.AnonymousCredentials() + load_creds.return_value = (file_creds, None) + adc.return_value = (creds, None) + client = client_class(client_options=options, transport=transport_name) + create_channel.assert_called_with( + "merchantapi.googleapis.com:443", + credentials=file_creds, + credentials_file=None, + quota_project_id=None, + default_scopes=("https://www.googleapis.com/auth/content",), + scopes=None, + default_host="merchantapi.googleapis.com", + ssl_credentials=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + +@pytest.mark.parametrize( + "request_type", + [ + productinputs.InsertProductInputRequest, + dict, + ], +) +def test_insert_product_input(request_type, transport: str = "grpc"): + client = ProductInputsServiceClient( + 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.insert_product_input), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = productinputs.ProductInput( + name="name_value", + product="product_value", + legacy_local=True, + offer_id="offer_id_value", + content_language="content_language_value", + feed_label="feed_label_value", + version_number=1518, + ) + response = client.insert_product_input(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + request = productinputs.InsertProductInputRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, productinputs.ProductInput) + assert response.name == "name_value" + assert response.product == "product_value" + assert response.legacy_local is True + assert response.offer_id == "offer_id_value" + assert response.content_language == "content_language_value" + assert response.feed_label == "feed_label_value" + assert response.version_number == 1518 + + +def test_insert_product_input_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 = ProductInputsServiceClient( + 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 = productinputs.InsertProductInputRequest( + parent="parent_value", + data_source="data_source_value", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.insert_product_input), "__call__" + ) as call: + call.return_value.name = ( + "foo" # operation_request.operation in compute client(s) expect a string. + ) + client.insert_product_input(request=request) + call.assert_called() + _, args, _ = call.mock_calls[0] + assert args[0] == productinputs.InsertProductInputRequest( + parent="parent_value", + data_source="data_source_value", + ) + + +def test_insert_product_input_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 = ProductInputsServiceClient( + 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.insert_product_input 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.insert_product_input + ] = mock_rpc + request = {} + client.insert_product_input(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.insert_product_input(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_insert_product_input_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 = ProductInputsServiceAsyncClient( + 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.insert_product_input + 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.insert_product_input + ] = mock_rpc + + request = {} + await client.insert_product_input(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + await client.insert_product_input(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_insert_product_input_async( + transport: str = "grpc_asyncio", + request_type=productinputs.InsertProductInputRequest, +): + client = ProductInputsServiceAsyncClient( + 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.insert_product_input), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + productinputs.ProductInput( + name="name_value", + product="product_value", + legacy_local=True, + offer_id="offer_id_value", + content_language="content_language_value", + feed_label="feed_label_value", + version_number=1518, + ) + ) + response = await client.insert_product_input(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + request = productinputs.InsertProductInputRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, productinputs.ProductInput) + assert response.name == "name_value" + assert response.product == "product_value" + assert response.legacy_local is True + assert response.offer_id == "offer_id_value" + assert response.content_language == "content_language_value" + assert response.feed_label == "feed_label_value" + assert response.version_number == 1518 + + +@pytest.mark.asyncio +async def test_insert_product_input_async_from_dict(): + await test_insert_product_input_async(request_type=dict) + + +def test_insert_product_input_field_headers(): + client = ProductInputsServiceClient( + 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 = productinputs.InsertProductInputRequest() + + request.parent = "parent_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.insert_product_input), "__call__" + ) as call: + call.return_value = productinputs.ProductInput() + client.insert_product_input(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", + "parent=parent_value", + ) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_insert_product_input_field_headers_async(): + client = ProductInputsServiceAsyncClient( + 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 = productinputs.InsertProductInputRequest() + + request.parent = "parent_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.insert_product_input), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + productinputs.ProductInput() + ) + await client.insert_product_input(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", + "parent=parent_value", + ) in kw["metadata"] + + +@pytest.mark.parametrize( + "request_type", + [ + productinputs.UpdateProductInputRequest, + dict, + ], +) +def test_update_product_input(request_type, transport: str = "grpc"): + client = ProductInputsServiceClient( + 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_product_input), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = productinputs.ProductInput( + name="name_value", + product="product_value", + legacy_local=True, + offer_id="offer_id_value", + content_language="content_language_value", + feed_label="feed_label_value", + version_number=1518, + ) + response = client.update_product_input(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + request = productinputs.UpdateProductInputRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, productinputs.ProductInput) + assert response.name == "name_value" + assert response.product == "product_value" + assert response.legacy_local is True + assert response.offer_id == "offer_id_value" + assert response.content_language == "content_language_value" + assert response.feed_label == "feed_label_value" + assert response.version_number == 1518 + + +def test_update_product_input_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 = ProductInputsServiceClient( + 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 = productinputs.UpdateProductInputRequest( + data_source="data_source_value", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.update_product_input), "__call__" + ) as call: + call.return_value.name = ( + "foo" # operation_request.operation in compute client(s) expect a string. + ) + client.update_product_input(request=request) + call.assert_called() + _, args, _ = call.mock_calls[0] + assert args[0] == productinputs.UpdateProductInputRequest( + data_source="data_source_value", + ) + + +def test_update_product_input_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 = ProductInputsServiceClient( + 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_product_input 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_product_input + ] = mock_rpc + request = {} + client.update_product_input(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.update_product_input(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_product_input_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 = ProductInputsServiceAsyncClient( + 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_product_input + 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_product_input + ] = mock_rpc + + request = {} + await client.update_product_input(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + await client.update_product_input(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_product_input_async( + transport: str = "grpc_asyncio", + request_type=productinputs.UpdateProductInputRequest, +): + client = ProductInputsServiceAsyncClient( + 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_product_input), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + productinputs.ProductInput( + name="name_value", + product="product_value", + legacy_local=True, + offer_id="offer_id_value", + content_language="content_language_value", + feed_label="feed_label_value", + version_number=1518, + ) + ) + response = await client.update_product_input(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + request = productinputs.UpdateProductInputRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, productinputs.ProductInput) + assert response.name == "name_value" + assert response.product == "product_value" + assert response.legacy_local is True + assert response.offer_id == "offer_id_value" + assert response.content_language == "content_language_value" + assert response.feed_label == "feed_label_value" + assert response.version_number == 1518 + + +@pytest.mark.asyncio +async def test_update_product_input_async_from_dict(): + await test_update_product_input_async(request_type=dict) + + +def test_update_product_input_field_headers(): + client = ProductInputsServiceClient( + 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 = productinputs.UpdateProductInputRequest() + + request.product_input.name = "name_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.update_product_input), "__call__" + ) as call: + call.return_value = productinputs.ProductInput() + client.update_product_input(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", + "product_input.name=name_value", + ) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_update_product_input_field_headers_async(): + client = ProductInputsServiceAsyncClient( + 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 = productinputs.UpdateProductInputRequest() + + request.product_input.name = "name_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.update_product_input), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + productinputs.ProductInput() + ) + await client.update_product_input(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", + "product_input.name=name_value", + ) in kw["metadata"] + + +def test_update_product_input_flattened(): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.update_product_input), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = productinputs.ProductInput() + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.update_product_input( + product_input=productinputs.ProductInput(name="name_value"), + 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].product_input + mock_val = productinputs.ProductInput(name="name_value") + 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_product_input_flattened_error(): + client = ProductInputsServiceClient( + 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_product_input( + productinputs.UpdateProductInputRequest(), + product_input=productinputs.ProductInput(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +@pytest.mark.asyncio +async def test_update_product_input_flattened_async(): + client = ProductInputsServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.update_product_input), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = productinputs.ProductInput() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + productinputs.ProductInput() + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.update_product_input( + product_input=productinputs.ProductInput(name="name_value"), + 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].product_input + mock_val = productinputs.ProductInput(name="name_value") + 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_product_input_flattened_error_async(): + client = ProductInputsServiceAsyncClient( + 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_product_input( + productinputs.UpdateProductInputRequest(), + product_input=productinputs.ProductInput(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +@pytest.mark.parametrize( + "request_type", + [ + productinputs.DeleteProductInputRequest, + dict, + ], +) +def test_delete_product_input(request_type, transport: str = "grpc"): + client = ProductInputsServiceClient( + 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.delete_product_input), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = None + response = client.delete_product_input(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + request = productinputs.DeleteProductInputRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_product_input_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 = ProductInputsServiceClient( + 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 = productinputs.DeleteProductInputRequest( + name="name_value", + data_source="data_source_value", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.delete_product_input), "__call__" + ) as call: + call.return_value.name = ( + "foo" # operation_request.operation in compute client(s) expect a string. + ) + client.delete_product_input(request=request) + call.assert_called() + _, args, _ = call.mock_calls[0] + assert args[0] == productinputs.DeleteProductInputRequest( + name="name_value", + data_source="data_source_value", + ) + + +def test_delete_product_input_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 = ProductInputsServiceClient( + 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.delete_product_input 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.delete_product_input + ] = mock_rpc + request = {} + client.delete_product_input(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.delete_product_input(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_delete_product_input_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 = ProductInputsServiceAsyncClient( + 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.delete_product_input + 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.delete_product_input + ] = mock_rpc + + request = {} + await client.delete_product_input(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + await client.delete_product_input(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_delete_product_input_async( + transport: str = "grpc_asyncio", + request_type=productinputs.DeleteProductInputRequest, +): + client = ProductInputsServiceAsyncClient( + 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.delete_product_input), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) + response = await client.delete_product_input(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + request = productinputs.DeleteProductInputRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert response is None + + +@pytest.mark.asyncio +async def test_delete_product_input_async_from_dict(): + await test_delete_product_input_async(request_type=dict) + + +def test_delete_product_input_field_headers(): + client = ProductInputsServiceClient( + 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 = productinputs.DeleteProductInputRequest() + + request.name = "name_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.delete_product_input), "__call__" + ) as call: + call.return_value = None + client.delete_product_input(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", + "name=name_value", + ) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_delete_product_input_field_headers_async(): + client = ProductInputsServiceAsyncClient( + 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 = productinputs.DeleteProductInputRequest() + + request.name = "name_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.delete_product_input), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) + await client.delete_product_input(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", + "name=name_value", + ) in kw["metadata"] + + +def test_delete_product_input_flattened(): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.delete_product_input), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = None + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.delete_product_input( + name="name_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].name + mock_val = "name_value" + assert arg == mock_val + + +def test_delete_product_input_flattened_error(): + client = ProductInputsServiceClient( + 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.delete_product_input( + productinputs.DeleteProductInputRequest(), + name="name_value", + ) + + +@pytest.mark.asyncio +async def test_delete_product_input_flattened_async(): + client = ProductInputsServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.delete_product_input), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = None + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.delete_product_input( + name="name_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].name + mock_val = "name_value" + assert arg == mock_val + + +@pytest.mark.asyncio +async def test_delete_product_input_flattened_error_async(): + client = ProductInputsServiceAsyncClient( + 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.delete_product_input( + productinputs.DeleteProductInputRequest(), + name="name_value", + ) + + +def test_insert_product_input_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 = ProductInputsServiceClient( + 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.insert_product_input 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.insert_product_input + ] = mock_rpc + + request = {} + client.insert_product_input(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.insert_product_input(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_insert_product_input_rest_required_fields( + request_type=productinputs.InsertProductInputRequest, +): + transport_class = transports.ProductInputsServiceRestTransport + + request_init = {} + request_init["parent"] = "" + request_init["data_source"] = "" + 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 + assert "dataSource" not in jsonified_request + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).insert_product_input._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + assert "dataSource" in jsonified_request + assert jsonified_request["dataSource"] == request_init["data_source"] + + jsonified_request["parent"] = "parent_value" + jsonified_request["dataSource"] = "data_source_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).insert_product_input._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("data_source",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + assert "dataSource" in jsonified_request + assert jsonified_request["dataSource"] == "data_source_value" + + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = productinputs.ProductInput() + # 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": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + # Convert return value to protobuf type + return_value = productinputs.ProductInput.pb(return_value) + 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.insert_product_input(request) + + expected_params = [ + ( + "dataSource", + "", + ), + ("$alt", "json;enum-encoding=int"), + ] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_insert_product_input_rest_unset_required_fields(): + transport = transports.ProductInputsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.insert_product_input._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("dataSource",)) + & set( + ( + "parent", + "productInput", + "dataSource", + ) + ) + ) + + +def test_update_product_input_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 = ProductInputsServiceClient( + 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_product_input 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_product_input + ] = mock_rpc + + request = {} + client.update_product_input(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.update_product_input(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_product_input_rest_required_fields( + request_type=productinputs.UpdateProductInputRequest, +): + transport_class = transports.ProductInputsServiceRestTransport + + request_init = {} + request_init["data_source"] = "" + 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 + assert "dataSource" not in jsonified_request + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_product_input._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + assert "dataSource" in jsonified_request + assert jsonified_request["dataSource"] == request_init["data_source"] + + jsonified_request["dataSource"] = "data_source_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_product_input._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "data_source", + "update_mask", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "dataSource" in jsonified_request + assert jsonified_request["dataSource"] == "data_source_value" + + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = productinputs.ProductInput() + # 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 + + # Convert return value to protobuf type + return_value = productinputs.ProductInput.pb(return_value) + 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_product_input(request) + + expected_params = [ + ( + "dataSource", + "", + ), + ("$alt", "json;enum-encoding=int"), + ] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_product_input_rest_unset_required_fields(): + transport = transports.ProductInputsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_product_input._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "dataSource", + "updateMask", + ) + ) + & set( + ( + "productInput", + "dataSource", + ) + ) + ) + + +def test_update_product_input_rest_flattened(): + client = ProductInputsServiceClient( + 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 = productinputs.ProductInput() + + # get arguments that satisfy an http rule for this method + sample_request = { + "product_input": {"name": "accounts/sample1/productInputs/sample2"} + } + + # get truthy value for each flattened field + mock_args = dict( + product_input=productinputs.ProductInput(name="name_value"), + 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 + # Convert return value to protobuf type + return_value = productinputs.ProductInput.pb(return_value) + 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_product_input(**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/products/v1/{product_input.name=accounts/*/productInputs/*}" + % client.transport._host, + args[1], + ) + + +def test_update_product_input_rest_flattened_error(transport: str = "rest"): + client = ProductInputsServiceClient( + 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_product_input( + productinputs.UpdateProductInputRequest(), + product_input=productinputs.ProductInput(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_delete_product_input_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 = ProductInputsServiceClient( + 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.delete_product_input 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.delete_product_input + ] = mock_rpc + + request = {} + client.delete_product_input(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.delete_product_input(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_delete_product_input_rest_required_fields( + request_type=productinputs.DeleteProductInputRequest, +): + transport_class = transports.ProductInputsServiceRestTransport + + request_init = {} + request_init["name"] = "" + request_init["data_source"] = "" + 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 + assert "dataSource" not in jsonified_request + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_product_input._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + assert "dataSource" in jsonified_request + assert jsonified_request["dataSource"] == request_init["data_source"] + + jsonified_request["name"] = "name_value" + jsonified_request["dataSource"] = "data_source_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_product_input._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("data_source",)) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + assert "dataSource" in jsonified_request + assert jsonified_request["dataSource"] == "data_source_value" + + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # 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": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_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.delete_product_input(request) + + expected_params = [ + ( + "dataSource", + "", + ), + ("$alt", "json;enum-encoding=int"), + ] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_product_input_rest_unset_required_fields(): + transport = transports.ProductInputsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_product_input._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("dataSource",)) + & set( + ( + "name", + "dataSource", + ) + ) + ) + + +def test_delete_product_input_rest_flattened(): + client = ProductInputsServiceClient( + 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 = None + + # get arguments that satisfy an http rule for this method + sample_request = {"name": "accounts/sample1/productInputs/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + name="name_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 = "" + 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.delete_product_input(**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/products/v1/{name=accounts/*/productInputs/*}" % client.transport._host, + args[1], + ) + + +def test_delete_product_input_rest_flattened_error(transport: str = "rest"): + client = ProductInputsServiceClient( + 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.delete_product_input( + productinputs.DeleteProductInputRequest(), + name="name_value", + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.ProductInputsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.ProductInputsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = ProductInputsServiceClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.ProductInputsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = ProductInputsServiceClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = ProductInputsServiceClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.ProductInputsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = ProductInputsServiceClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.ProductInputsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = ProductInputsServiceClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.ProductInputsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.ProductInputsServiceGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.ProductInputsServiceGrpcTransport, + transports.ProductInputsServiceGrpcAsyncIOTransport, + transports.ProductInputsServiceRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +def test_transport_kind_grpc(): + transport = ProductInputsServiceClient.get_transport_class("grpc")( + credentials=ga_credentials.AnonymousCredentials() + ) + assert transport.kind == "grpc" + + +def test_initialize_client_w_grpc(): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="grpc" + ) + assert client is not None + + +# 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_insert_product_input_empty_call_grpc(): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.insert_product_input), "__call__" + ) as call: + call.return_value = productinputs.ProductInput() + client.insert_product_input(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = productinputs.InsertProductInputRequest() + + 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_product_input_empty_call_grpc(): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.update_product_input), "__call__" + ) as call: + call.return_value = productinputs.ProductInput() + client.update_product_input(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = productinputs.UpdateProductInputRequest() + + 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_product_input_empty_call_grpc(): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.delete_product_input), "__call__" + ) as call: + call.return_value = None + client.delete_product_input(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = productinputs.DeleteProductInputRequest() + + assert args[0] == request_msg + + +def test_transport_kind_grpc_asyncio(): + transport = ProductInputsServiceAsyncClient.get_transport_class("grpc_asyncio")( + credentials=async_anonymous_credentials() + ) + assert transport.kind == "grpc_asyncio" + + +def test_initialize_client_w_grpc_asyncio(): + client = ProductInputsServiceAsyncClient( + credentials=async_anonymous_credentials(), transport="grpc_asyncio" + ) + assert client is not None + + +# 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_insert_product_input_empty_call_grpc_asyncio(): + client = ProductInputsServiceAsyncClient( + credentials=async_anonymous_credentials(), + transport="grpc_asyncio", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.insert_product_input), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + productinputs.ProductInput( + name="name_value", + product="product_value", + legacy_local=True, + offer_id="offer_id_value", + content_language="content_language_value", + feed_label="feed_label_value", + version_number=1518, + ) + ) + await client.insert_product_input(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = productinputs.InsertProductInputRequest() + + 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_update_product_input_empty_call_grpc_asyncio(): + client = ProductInputsServiceAsyncClient( + credentials=async_anonymous_credentials(), + transport="grpc_asyncio", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.update_product_input), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + productinputs.ProductInput( + name="name_value", + product="product_value", + legacy_local=True, + offer_id="offer_id_value", + content_language="content_language_value", + feed_label="feed_label_value", + version_number=1518, + ) + ) + await client.update_product_input(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = productinputs.UpdateProductInputRequest() + + 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_delete_product_input_empty_call_grpc_asyncio(): + client = ProductInputsServiceAsyncClient( + credentials=async_anonymous_credentials(), + transport="grpc_asyncio", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.delete_product_input), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) + await client.delete_product_input(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = productinputs.DeleteProductInputRequest() + + assert args[0] == request_msg + + +def test_transport_kind_rest(): + transport = ProductInputsServiceClient.get_transport_class("rest")( + credentials=ga_credentials.AnonymousCredentials() + ) + assert transport.kind == "rest" + + +def test_insert_product_input_rest_bad_request( + request_type=productinputs.InsertProductInputRequest, +): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + # send a request that will satisfy transcoding + request_init = {"parent": "accounts/sample1"} + 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.insert_product_input(request) + + +@pytest.mark.parametrize( + "request_type", + [ + productinputs.InsertProductInputRequest, + dict, + ], +) +def test_insert_product_input_rest_call_success(request_type): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "accounts/sample1"} + request_init["product_input"] = { + "name": "name_value", + "product": "product_value", + "legacy_local": True, + "offer_id": "offer_id_value", + "content_language": "content_language_value", + "feed_label": "feed_label_value", + "version_number": 1518, + "product_attributes": { + "identifier_exists": True, + "is_bundle": True, + "title": "title_value", + "description": "description_value", + "link": "link_value", + "mobile_link": "mobile_link_value", + "canonical_link": "canonical_link_value", + "image_link": "image_link_value", + "additional_image_links": [ + "additional_image_links_value1", + "additional_image_links_value2", + ], + "expiration_date": {"seconds": 751, "nanos": 543}, + "disclosure_date": {}, + "adult": True, + "age_group": 1, + "availability": 1, + "availability_date": {}, + "brand": "brand_value", + "color": "color_value", + "condition": 1, + "gender": 1, + "google_product_category": "google_product_category_value", + "gtins": ["gtins_value1", "gtins_value2"], + "item_group_id": "item_group_id_value", + "material": "material_value", + "mpn": "mpn_value", + "pattern": "pattern_value", + "price": {"amount_micros": 1408, "currency_code": "currency_code_value"}, + "maximum_retail_price": {}, + "installment": { + "months": 665, + "amount": {}, + "downpayment": {}, + "credit_type": 1, + }, + "subscription_cost": {"period": 1, "period_length": 1380, "amount": {}}, + "loyalty_points": { + "name": "name_value", + "points_value": 1305, + "ratio": 0.543, + }, + "loyalty_programs": [ + { + "program_label": "program_label_value", + "tier_label": "tier_label_value", + "price": {}, + "cashback_for_future_use": {}, + "loyalty_points": 1546, + "member_price_effective_date": {"start_time": {}, "end_time": {}}, + "shipping_label": "shipping_label_value", + } + ], + "product_types": ["product_types_value1", "product_types_value2"], + "sale_price": {}, + "sale_price_effective_date": {}, + "sell_on_google_quantity": 2470, + "product_height": {"value": 0.541, "unit": "unit_value"}, + "product_length": {}, + "product_width": {}, + "product_weight": {"value": 0.541, "unit": "unit_value"}, + "shipping": [ + { + "price": {}, + "country": "country_value", + "region": "region_value", + "service": "service_value", + "location_id": 1157, + "location_group_name": "location_group_name_value", + "postal_code": "postal_code_value", + "min_handling_time": 1782, + "max_handling_time": 1784, + "min_transit_time": 1718, + "max_transit_time": 1720, + } + ], + "free_shipping_threshold": [ + {"country": "country_value", "price_threshold": {}} + ], + "shipping_weight": {"value": 0.541, "unit": "unit_value"}, + "shipping_length": {"value": 0.541, "unit": "unit_value"}, + "shipping_width": {}, + "shipping_height": {}, + "max_handling_time": 1784, + "min_handling_time": 1782, + "shipping_label": "shipping_label_value", + "transit_time_label": "transit_time_label_value", + "size": "size_value", + "size_system": 1, + "size_types": [1], + "energy_efficiency_class": 1, + "min_energy_efficiency_class": 1, + "max_energy_efficiency_class": 1, + "unit_pricing_measure": {"value": 0.541, "unit": "unit_value"}, + "unit_pricing_base_measure": {"value": 541, "unit": "unit_value"}, + "multipack": 970, + "ads_grouping": "ads_grouping_value", + "ads_labels": ["ads_labels_value1", "ads_labels_value2"], + "ads_redirect": "ads_redirect_value", + "cost_of_goods_sold": {}, + "product_details": [ + { + "section_name": "section_name_value", + "attribute_name": "attribute_name_value", + "attribute_value": "attribute_value_value", + } + ], + "product_highlights": [ + "product_highlights_value1", + "product_highlights_value2", + ], + "display_ads_id": "display_ads_id_value", + "display_ads_similar_ids": [ + "display_ads_similar_ids_value1", + "display_ads_similar_ids_value2", + ], + "display_ads_title": "display_ads_title_value", + "display_ads_link": "display_ads_link_value", + "display_ads_value": 0.1801, + "promotion_ids": ["promotion_ids_value1", "promotion_ids_value2"], + "pickup_method": 1, + "pickup_sla": 1, + "link_template": "link_template_value", + "mobile_link_template": "mobile_link_template_value", + "custom_label_0": "custom_label_0_value", + "custom_label_1": "custom_label_1_value", + "custom_label_2": "custom_label_2_value", + "custom_label_3": "custom_label_3_value", + "custom_label_4": "custom_label_4_value", + "included_destinations": [1], + "excluded_destinations": [1], + "shopping_ads_excluded_countries": [ + "shopping_ads_excluded_countries_value1", + "shopping_ads_excluded_countries_value2", + ], + "external_seller_id": "external_seller_id_value", + "pause": 1, + "lifestyle_image_links": [ + "lifestyle_image_links_value1", + "lifestyle_image_links_value2", + ], + "cloud_export_additional_properties": [ + { + "property_name": "property_name_value", + "text_value": ["text_value_value1", "text_value_value2"], + "bool_value": True, + "int_value": [968, 969], + "float_value": [0.11710000000000001, 0.11720000000000001], + "min_value": 0.96, + "max_value": 0.962, + "unit_code": "unit_code_value", + } + ], + "virtual_model_link": "virtual_model_link_value", + "certifications": [ + { + "certification_authority": 1, + "certification_name": 1, + "certification_code": "certification_code_value", + "certification_value": "certification_value_value", + } + ], + "structured_title": {"digital_source_type": 1, "content": "content_value"}, + "structured_description": { + "digital_source_type": 1, + "content": "content_value", + }, + "auto_pricing_min_price": {}, + "sustainability_incentives": [ + {"amount": {}, "percentage": 0.10540000000000001, "type_": 1} + ], + }, + "custom_attributes": [ + {"name": "name_value", "value": "value_value", "group_values": {}} + ], + } + # 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 = productinputs.InsertProductInputRequest.meta.fields["product_input"] + + 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["product_input"].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["product_input"][field])): + del request_init["product_input"][field][i][subfield] + else: + del request_init["product_input"][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 = productinputs.ProductInput( + name="name_value", + product="product_value", + legacy_local=True, + offer_id="offer_id_value", + content_language="content_language_value", + feed_label="feed_label_value", + version_number=1518, + ) + + # Wrap the value into a proper Response obj + response_value = mock.Mock() + response_value.status_code = 200 + + # Convert return value to protobuf type + return_value = productinputs.ProductInput.pb(return_value) + 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.insert_product_input(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, productinputs.ProductInput) + assert response.name == "name_value" + assert response.product == "product_value" + assert response.legacy_local is True + assert response.offer_id == "offer_id_value" + assert response.content_language == "content_language_value" + assert response.feed_label == "feed_label_value" + assert response.version_number == 1518 + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_insert_product_input_rest_interceptors(null_interceptor): + transport = transports.ProductInputsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ProductInputsServiceRestInterceptor(), + ) + client = ProductInputsServiceClient(transport=transport) + + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ProductInputsServiceRestInterceptor, "post_insert_product_input" + ) as post, mock.patch.object( + transports.ProductInputsServiceRestInterceptor, + "post_insert_product_input_with_metadata", + ) as post_with_metadata, mock.patch.object( + transports.ProductInputsServiceRestInterceptor, "pre_insert_product_input" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + post_with_metadata.assert_not_called() + pb_message = productinputs.InsertProductInputRequest.pb( + productinputs.InsertProductInputRequest() + ) + 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 = productinputs.ProductInput.to_json(productinputs.ProductInput()) + req.return_value.content = return_value + + request = productinputs.InsertProductInputRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = productinputs.ProductInput() + post_with_metadata.return_value = productinputs.ProductInput(), metadata + + client.insert_product_input( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + post_with_metadata.assert_called_once() + + +def test_update_product_input_rest_bad_request( + request_type=productinputs.UpdateProductInputRequest, +): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + # send a request that will satisfy transcoding + request_init = {"product_input": {"name": "accounts/sample1/productInputs/sample2"}} + 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_product_input(request) + + +@pytest.mark.parametrize( + "request_type", + [ + productinputs.UpdateProductInputRequest, + dict, + ], +) +def test_update_product_input_rest_call_success(request_type): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + # send a request that will satisfy transcoding + request_init = {"product_input": {"name": "accounts/sample1/productInputs/sample2"}} + request_init["product_input"] = { + "name": "accounts/sample1/productInputs/sample2", + "product": "product_value", + "legacy_local": True, + "offer_id": "offer_id_value", + "content_language": "content_language_value", + "feed_label": "feed_label_value", + "version_number": 1518, + "product_attributes": { + "identifier_exists": True, + "is_bundle": True, + "title": "title_value", + "description": "description_value", + "link": "link_value", + "mobile_link": "mobile_link_value", + "canonical_link": "canonical_link_value", + "image_link": "image_link_value", + "additional_image_links": [ + "additional_image_links_value1", + "additional_image_links_value2", + ], + "expiration_date": {"seconds": 751, "nanos": 543}, + "disclosure_date": {}, + "adult": True, + "age_group": 1, + "availability": 1, + "availability_date": {}, + "brand": "brand_value", + "color": "color_value", + "condition": 1, + "gender": 1, + "google_product_category": "google_product_category_value", + "gtins": ["gtins_value1", "gtins_value2"], + "item_group_id": "item_group_id_value", + "material": "material_value", + "mpn": "mpn_value", + "pattern": "pattern_value", + "price": {"amount_micros": 1408, "currency_code": "currency_code_value"}, + "maximum_retail_price": {}, + "installment": { + "months": 665, + "amount": {}, + "downpayment": {}, + "credit_type": 1, + }, + "subscription_cost": {"period": 1, "period_length": 1380, "amount": {}}, + "loyalty_points": { + "name": "name_value", + "points_value": 1305, + "ratio": 0.543, + }, + "loyalty_programs": [ + { + "program_label": "program_label_value", + "tier_label": "tier_label_value", + "price": {}, + "cashback_for_future_use": {}, + "loyalty_points": 1546, + "member_price_effective_date": {"start_time": {}, "end_time": {}}, + "shipping_label": "shipping_label_value", + } + ], + "product_types": ["product_types_value1", "product_types_value2"], + "sale_price": {}, + "sale_price_effective_date": {}, + "sell_on_google_quantity": 2470, + "product_height": {"value": 0.541, "unit": "unit_value"}, + "product_length": {}, + "product_width": {}, + "product_weight": {"value": 0.541, "unit": "unit_value"}, + "shipping": [ + { + "price": {}, + "country": "country_value", + "region": "region_value", + "service": "service_value", + "location_id": 1157, + "location_group_name": "location_group_name_value", + "postal_code": "postal_code_value", + "min_handling_time": 1782, + "max_handling_time": 1784, + "min_transit_time": 1718, + "max_transit_time": 1720, + } + ], + "free_shipping_threshold": [ + {"country": "country_value", "price_threshold": {}} + ], + "shipping_weight": {"value": 0.541, "unit": "unit_value"}, + "shipping_length": {"value": 0.541, "unit": "unit_value"}, + "shipping_width": {}, + "shipping_height": {}, + "max_handling_time": 1784, + "min_handling_time": 1782, + "shipping_label": "shipping_label_value", + "transit_time_label": "transit_time_label_value", + "size": "size_value", + "size_system": 1, + "size_types": [1], + "energy_efficiency_class": 1, + "min_energy_efficiency_class": 1, + "max_energy_efficiency_class": 1, + "unit_pricing_measure": {"value": 0.541, "unit": "unit_value"}, + "unit_pricing_base_measure": {"value": 541, "unit": "unit_value"}, + "multipack": 970, + "ads_grouping": "ads_grouping_value", + "ads_labels": ["ads_labels_value1", "ads_labels_value2"], + "ads_redirect": "ads_redirect_value", + "cost_of_goods_sold": {}, + "product_details": [ + { + "section_name": "section_name_value", + "attribute_name": "attribute_name_value", + "attribute_value": "attribute_value_value", + } + ], + "product_highlights": [ + "product_highlights_value1", + "product_highlights_value2", + ], + "display_ads_id": "display_ads_id_value", + "display_ads_similar_ids": [ + "display_ads_similar_ids_value1", + "display_ads_similar_ids_value2", + ], + "display_ads_title": "display_ads_title_value", + "display_ads_link": "display_ads_link_value", + "display_ads_value": 0.1801, + "promotion_ids": ["promotion_ids_value1", "promotion_ids_value2"], + "pickup_method": 1, + "pickup_sla": 1, + "link_template": "link_template_value", + "mobile_link_template": "mobile_link_template_value", + "custom_label_0": "custom_label_0_value", + "custom_label_1": "custom_label_1_value", + "custom_label_2": "custom_label_2_value", + "custom_label_3": "custom_label_3_value", + "custom_label_4": "custom_label_4_value", + "included_destinations": [1], + "excluded_destinations": [1], + "shopping_ads_excluded_countries": [ + "shopping_ads_excluded_countries_value1", + "shopping_ads_excluded_countries_value2", + ], + "external_seller_id": "external_seller_id_value", + "pause": 1, + "lifestyle_image_links": [ + "lifestyle_image_links_value1", + "lifestyle_image_links_value2", + ], + "cloud_export_additional_properties": [ + { + "property_name": "property_name_value", + "text_value": ["text_value_value1", "text_value_value2"], + "bool_value": True, + "int_value": [968, 969], + "float_value": [0.11710000000000001, 0.11720000000000001], + "min_value": 0.96, + "max_value": 0.962, + "unit_code": "unit_code_value", + } + ], + "virtual_model_link": "virtual_model_link_value", + "certifications": [ + { + "certification_authority": 1, + "certification_name": 1, + "certification_code": "certification_code_value", + "certification_value": "certification_value_value", + } + ], + "structured_title": {"digital_source_type": 1, "content": "content_value"}, + "structured_description": { + "digital_source_type": 1, + "content": "content_value", + }, + "auto_pricing_min_price": {}, + "sustainability_incentives": [ + {"amount": {}, "percentage": 0.10540000000000001, "type_": 1} + ], + }, + "custom_attributes": [ + {"name": "name_value", "value": "value_value", "group_values": {}} + ], + } + # 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 = productinputs.UpdateProductInputRequest.meta.fields["product_input"] + + 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["product_input"].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["product_input"][field])): + del request_init["product_input"][field][i][subfield] + else: + del request_init["product_input"][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 = productinputs.ProductInput( + name="name_value", + product="product_value", + legacy_local=True, + offer_id="offer_id_value", + content_language="content_language_value", + feed_label="feed_label_value", + version_number=1518, + ) + + # Wrap the value into a proper Response obj + response_value = mock.Mock() + response_value.status_code = 200 + + # Convert return value to protobuf type + return_value = productinputs.ProductInput.pb(return_value) + 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_product_input(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, productinputs.ProductInput) + assert response.name == "name_value" + assert response.product == "product_value" + assert response.legacy_local is True + assert response.offer_id == "offer_id_value" + assert response.content_language == "content_language_value" + assert response.feed_label == "feed_label_value" + assert response.version_number == 1518 + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_product_input_rest_interceptors(null_interceptor): + transport = transports.ProductInputsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ProductInputsServiceRestInterceptor(), + ) + client = ProductInputsServiceClient(transport=transport) + + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ProductInputsServiceRestInterceptor, "post_update_product_input" + ) as post, mock.patch.object( + transports.ProductInputsServiceRestInterceptor, + "post_update_product_input_with_metadata", + ) as post_with_metadata, mock.patch.object( + transports.ProductInputsServiceRestInterceptor, "pre_update_product_input" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + post_with_metadata.assert_not_called() + pb_message = productinputs.UpdateProductInputRequest.pb( + productinputs.UpdateProductInputRequest() + ) + 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 = productinputs.ProductInput.to_json(productinputs.ProductInput()) + req.return_value.content = return_value + + request = productinputs.UpdateProductInputRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = productinputs.ProductInput() + post_with_metadata.return_value = productinputs.ProductInput(), metadata + + client.update_product_input( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + post_with_metadata.assert_called_once() + + +def test_delete_product_input_rest_bad_request( + request_type=productinputs.DeleteProductInputRequest, +): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + # send a request that will satisfy transcoding + request_init = {"name": "accounts/sample1/productInputs/sample2"} + 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.delete_product_input(request) + + +@pytest.mark.parametrize( + "request_type", + [ + productinputs.DeleteProductInputRequest, + dict, + ], +) +def test_delete_product_input_rest_call_success(request_type): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + # send a request that will satisfy transcoding + request_init = {"name": "accounts/sample1/productInputs/sample2"} + 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 = None + + # Wrap the value into a proper Response obj + response_value = mock.Mock() + response_value.status_code = 200 + json_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.delete_product_input(request) + + # Establish that the response is the type that we expect. + assert response is None + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_product_input_rest_interceptors(null_interceptor): + transport = transports.ProductInputsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ProductInputsServiceRestInterceptor(), + ) + client = ProductInputsServiceClient(transport=transport) + + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ProductInputsServiceRestInterceptor, "pre_delete_product_input" + ) as pre: + pre.assert_not_called() + pb_message = productinputs.DeleteProductInputRequest.pb( + productinputs.DeleteProductInputRequest() + ) + 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"} + + request = productinputs.DeleteProductInputRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_product_input( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_initialize_client_w_rest(): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + assert client is not None + + +# 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_insert_product_input_empty_call_rest(): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.insert_product_input), "__call__" + ) as call: + client.insert_product_input(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = productinputs.InsertProductInputRequest() + + 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_product_input_empty_call_rest(): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.update_product_input), "__call__" + ) as call: + client.update_product_input(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = productinputs.UpdateProductInputRequest() + + 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_product_input_empty_call_rest(): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.delete_product_input), "__call__" + ) as call: + client.delete_product_input(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = productinputs.DeleteProductInputRequest() + + assert args[0] == request_msg + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.ProductInputsServiceGrpcTransport, + ) + + +def test_product_inputs_service_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.ProductInputsServiceTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_product_inputs_service_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.shopping.merchant_products_v1.services.product_inputs_service.transports.ProductInputsServiceTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.ProductInputsServiceTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "insert_product_input", + "update_product_input", + "delete_product_input", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + with pytest.raises(NotImplementedError): + transport.close() + + # Catch all for all remaining methods and properties + remainder = [ + "kind", + ] + for r in remainder: + with pytest.raises(NotImplementedError): + getattr(transport, r)() + + +def test_product_inputs_service_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch( + "google.shopping.merchant_products_v1.services.product_inputs_service.transports.ProductInputsServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.ProductInputsServiceTransport( + credentials_file="credentials.json", + quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=None, + default_scopes=("https://www.googleapis.com/auth/content",), + quota_project_id="octopus", + ) + + +def test_product_inputs_service_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( + "google.shopping.merchant_products_v1.services.product_inputs_service.transports.ProductInputsServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.ProductInputsServiceTransport() + adc.assert_called_once() + + +def test_product_inputs_service_auth_adc(): + # If no credentials are provided, we should use ADC credentials. + with mock.patch.object(google.auth, "default", autospec=True) as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + ProductInputsServiceClient() + adc.assert_called_once_with( + scopes=None, + default_scopes=("https://www.googleapis.com/auth/content",), + quota_project_id=None, + ) + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.ProductInputsServiceGrpcTransport, + transports.ProductInputsServiceGrpcAsyncIOTransport, + ], +) +def test_product_inputs_service_transport_auth_adc(transport_class): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object(google.auth, "default", autospec=True) as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class(quota_project_id="octopus", scopes=["1", "2"]) + adc.assert_called_once_with( + scopes=["1", "2"], + default_scopes=("https://www.googleapis.com/auth/content",), + quota_project_id="octopus", + ) + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.ProductInputsServiceGrpcTransport, + transports.ProductInputsServiceGrpcAsyncIOTransport, + transports.ProductInputsServiceRestTransport, + ], +) +def test_product_inputs_service_transport_auth_gdch_credentials(transport_class): + host = "https://language.com" + api_audience_tests = [None, "https://language2.com"] + api_audience_expect = [host, "https://language2.com"] + for t, e in zip(api_audience_tests, api_audience_expect): + with mock.patch.object(google.auth, "default", autospec=True) as adc: + gdch_mock = mock.MagicMock() + type(gdch_mock).with_gdch_audience = mock.PropertyMock( + return_value=gdch_mock + ) + adc.return_value = (gdch_mock, None) + transport_class(host=host, api_audience=t) + gdch_mock.with_gdch_audience.assert_called_once_with(e) + + +@pytest.mark.parametrize( + "transport_class,grpc_helpers", + [ + (transports.ProductInputsServiceGrpcTransport, grpc_helpers), + (transports.ProductInputsServiceGrpcAsyncIOTransport, grpc_helpers_async), + ], +) +def test_product_inputs_service_transport_create_channel(transport_class, grpc_helpers): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object( + google.auth, "default", autospec=True + ) as adc, mock.patch.object( + grpc_helpers, "create_channel", autospec=True + ) as create_channel: + creds = ga_credentials.AnonymousCredentials() + adc.return_value = (creds, None) + transport_class(quota_project_id="octopus", scopes=["1", "2"]) + + create_channel.assert_called_with( + "merchantapi.googleapis.com:443", + credentials=creds, + credentials_file=None, + quota_project_id="octopus", + default_scopes=("https://www.googleapis.com/auth/content",), + scopes=["1", "2"], + default_host="merchantapi.googleapis.com", + ssl_credentials=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.ProductInputsServiceGrpcTransport, + transports.ProductInputsServiceGrpcAsyncIOTransport, + ], +) +def test_product_inputs_service_grpc_transport_client_cert_source_for_mtls( + transport_class, +): + cred = ga_credentials.AnonymousCredentials() + + # Check ssl_channel_credentials is used if provided. + with mock.patch.object(transport_class, "create_channel") as mock_create_channel: + mock_ssl_channel_creds = mock.Mock() + transport_class( + host="squid.clam.whelk", + credentials=cred, + ssl_channel_credentials=mock_ssl_channel_creds, + ) + mock_create_channel.assert_called_once_with( + "squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=None, + ssl_credentials=mock_ssl_channel_creds, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + # Check if ssl_channel_credentials is not provided, then client_cert_source_for_mtls + # is used. + with mock.patch.object(transport_class, "create_channel", return_value=mock.Mock()): + with mock.patch("grpc.ssl_channel_credentials") as mock_ssl_cred: + transport_class( + credentials=cred, + client_cert_source_for_mtls=client_cert_source_callback, + ) + expected_cert, expected_key = client_cert_source_callback() + mock_ssl_cred.assert_called_once_with( + certificate_chain=expected_cert, private_key=expected_key + ) + + +def test_product_inputs_service_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.ProductInputsServiceRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "grpc_asyncio", + "rest", + ], +) +def test_product_inputs_service_host_no_port(transport_name): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions( + api_endpoint="merchantapi.googleapis.com" + ), + transport=transport_name, + ) + assert client.transport._host == ( + "merchantapi.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://merchantapi.googleapis.com" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "grpc_asyncio", + "rest", + ], +) +def test_product_inputs_service_host_with_port(transport_name): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions( + api_endpoint="merchantapi.googleapis.com:8000" + ), + transport=transport_name, + ) + assert client.transport._host == ( + "merchantapi.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://merchantapi.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_product_inputs_service_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = ProductInputsServiceClient( + credentials=creds1, + transport=transport_name, + ) + client2 = ProductInputsServiceClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.insert_product_input._session + session2 = client2.transport.insert_product_input._session + assert session1 != session2 + session1 = client1.transport.update_product_input._session + session2 = client2.transport.update_product_input._session + assert session1 != session2 + session1 = client1.transport.delete_product_input._session + session2 = client2.transport.delete_product_input._session + assert session1 != session2 + + +def test_product_inputs_service_grpc_transport_channel(): + channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials()) + + # Check that channel is used if provided. + transport = transports.ProductInputsServiceGrpcTransport( + host="squid.clam.whelk", + channel=channel, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + assert transport._ssl_channel_credentials == None + + +def test_product_inputs_service_grpc_asyncio_transport_channel(): + channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials()) + + # Check that channel is used if provided. + transport = transports.ProductInputsServiceGrpcAsyncIOTransport( + host="squid.clam.whelk", + channel=channel, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + assert transport._ssl_channel_credentials == None + + +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. +@pytest.mark.parametrize( + "transport_class", + [ + transports.ProductInputsServiceGrpcTransport, + transports.ProductInputsServiceGrpcAsyncIOTransport, + ], +) +def test_product_inputs_service_transport_channel_mtls_with_client_cert_source( + transport_class, +): + with mock.patch( + "grpc.ssl_channel_credentials", autospec=True + ) as grpc_ssl_channel_cred: + with mock.patch.object( + transport_class, "create_channel" + ) as grpc_create_channel: + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + cred = ga_credentials.AnonymousCredentials() + with pytest.warns(DeprecationWarning): + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (cred, None) + transport = transport_class( + host="squid.clam.whelk", + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + adc.assert_called_once() + + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=None, + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + assert transport.grpc_channel == mock_grpc_channel + assert transport._ssl_channel_credentials == mock_ssl_cred + + +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. +@pytest.mark.parametrize( + "transport_class", + [ + transports.ProductInputsServiceGrpcTransport, + transports.ProductInputsServiceGrpcAsyncIOTransport, + ], +) +def test_product_inputs_service_transport_channel_mtls_with_adc(transport_class): + mock_ssl_cred = mock.Mock() + with mock.patch.multiple( + "google.auth.transport.grpc.SslCredentials", + __init__=mock.Mock(return_value=None), + ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), + ): + with mock.patch.object( + transport_class, "create_channel" + ) as grpc_create_channel: + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + mock_cred = mock.Mock() + + with pytest.warns(DeprecationWarning): + transport = transport_class( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=None, + ) + + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + credentials_file=None, + scopes=None, + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + assert transport.grpc_channel == mock_grpc_channel + + +def test_product_path(): + account = "squid" + product = "clam" + expected = "accounts/{account}/products/{product}".format( + account=account, + product=product, + ) + actual = ProductInputsServiceClient.product_path(account, product) + assert expected == actual + + +def test_parse_product_path(): + expected = { + "account": "whelk", + "product": "octopus", + } + path = ProductInputsServiceClient.product_path(**expected) + + # Check that the path construction is reversible. + actual = ProductInputsServiceClient.parse_product_path(path) + assert expected == actual + + +def test_product_input_path(): + account = "oyster" + productinput = "nudibranch" + expected = "accounts/{account}/productInputs/{productinput}".format( + account=account, + productinput=productinput, + ) + actual = ProductInputsServiceClient.product_input_path(account, productinput) + assert expected == actual + + +def test_parse_product_input_path(): + expected = { + "account": "cuttlefish", + "productinput": "mussel", + } + path = ProductInputsServiceClient.product_input_path(**expected) + + # Check that the path construction is reversible. + actual = ProductInputsServiceClient.parse_product_input_path(path) + assert expected == actual + + +def test_common_billing_account_path(): + billing_account = "winkle" + expected = "billingAccounts/{billing_account}".format( + billing_account=billing_account, + ) + actual = ProductInputsServiceClient.common_billing_account_path(billing_account) + assert expected == actual + + +def test_parse_common_billing_account_path(): + expected = { + "billing_account": "nautilus", + } + path = ProductInputsServiceClient.common_billing_account_path(**expected) + + # Check that the path construction is reversible. + actual = ProductInputsServiceClient.parse_common_billing_account_path(path) + assert expected == actual + + +def test_common_folder_path(): + folder = "scallop" + expected = "folders/{folder}".format( + folder=folder, + ) + actual = ProductInputsServiceClient.common_folder_path(folder) + assert expected == actual + + +def test_parse_common_folder_path(): + expected = { + "folder": "abalone", + } + path = ProductInputsServiceClient.common_folder_path(**expected) + + # Check that the path construction is reversible. + actual = ProductInputsServiceClient.parse_common_folder_path(path) + assert expected == actual + + +def test_common_organization_path(): + organization = "squid" + expected = "organizations/{organization}".format( + organization=organization, + ) + actual = ProductInputsServiceClient.common_organization_path(organization) + assert expected == actual + + +def test_parse_common_organization_path(): + expected = { + "organization": "clam", + } + path = ProductInputsServiceClient.common_organization_path(**expected) + + # Check that the path construction is reversible. + actual = ProductInputsServiceClient.parse_common_organization_path(path) + assert expected == actual + + +def test_common_project_path(): + project = "whelk" + expected = "projects/{project}".format( + project=project, + ) + actual = ProductInputsServiceClient.common_project_path(project) + assert expected == actual + + +def test_parse_common_project_path(): + expected = { + "project": "octopus", + } + path = ProductInputsServiceClient.common_project_path(**expected) + + # Check that the path construction is reversible. + actual = ProductInputsServiceClient.parse_common_project_path(path) + assert expected == actual + + +def test_common_location_path(): + project = "oyster" + location = "nudibranch" + expected = "projects/{project}/locations/{location}".format( + project=project, + location=location, + ) + actual = ProductInputsServiceClient.common_location_path(project, location) + assert expected == actual + + +def test_parse_common_location_path(): + expected = { + "project": "cuttlefish", + "location": "mussel", + } + path = ProductInputsServiceClient.common_location_path(**expected) + + # Check that the path construction is reversible. + actual = ProductInputsServiceClient.parse_common_location_path(path) + assert expected == actual + + +def test_client_with_default_client_info(): + client_info = gapic_v1.client_info.ClientInfo() + + with mock.patch.object( + transports.ProductInputsServiceTransport, "_prep_wrapped_messages" + ) as prep: + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + with mock.patch.object( + transports.ProductInputsServiceTransport, "_prep_wrapped_messages" + ) as prep: + transport_class = ProductInputsServiceClient.get_transport_class() + transport = transport_class( + credentials=ga_credentials.AnonymousCredentials(), + client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + +def test_transport_close_grpc(): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="grpc" + ) + with mock.patch.object( + type(getattr(client.transport, "_grpc_channel")), "close" + ) as close: + with client: + close.assert_not_called() + close.assert_called_once() + + +@pytest.mark.asyncio +async def test_transport_close_grpc_asyncio(): + client = ProductInputsServiceAsyncClient( + credentials=async_anonymous_credentials(), transport="grpc_asyncio" + ) + with mock.patch.object( + type(getattr(client.transport, "_grpc_channel")), "close" + ) as close: + async with client: + close.assert_not_called() + close.assert_called_once() + + +def test_transport_close_rest(): + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + with mock.patch.object( + type(getattr(client.transport, "_session")), "close" + ) as close: + with client: + close.assert_not_called() + close.assert_called_once() + + +def test_client_ctx(): + transports = [ + "rest", + "grpc", + ] + for transport in transports: + client = ProductInputsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport=transport + ) + # Test client calls underlying transport. + with mock.patch.object(type(client.transport), "close") as close: + close.assert_not_called() + with client: + pass + close.assert_called() + + +@pytest.mark.parametrize( + "client_class,transport_class", + [ + (ProductInputsServiceClient, transports.ProductInputsServiceGrpcTransport), + ( + ProductInputsServiceAsyncClient, + transports.ProductInputsServiceGrpcAsyncIOTransport, + ), + ], +) +def test_api_key_credentials(client_class, transport_class): + with mock.patch.object( + google.auth._default, "get_api_key_credentials", create=True + ) as get_api_key_credentials: + mock_cred = mock.Mock() + get_api_key_credentials.return_value = mock_cred + options = client_options.ClientOptions() + options.api_key = "api_key" + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=mock_cred, + credentials_file=None, + host=client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ), + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) diff --git a/packages/google-shopping-merchant-products/tests/unit/gapic/merchant_products_v1/test_products_service.py b/packages/google-shopping-merchant-products/tests/unit/gapic/merchant_products_v1/test_products_service.py new file mode 100644 index 000000000000..21e9a8631644 --- /dev/null +++ b/packages/google-shopping-merchant-products/tests/unit/gapic/merchant_products_v1/test_products_service.py @@ -0,0 +1,3622 @@ +# -*- 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. +# +import os + +# try/except added for compatibility with python < 3.8 +try: + from unittest import mock + from unittest.mock import AsyncMock # pragma: NO COVER +except ImportError: # pragma: NO COVER + import mock + +from collections.abc import AsyncIterable, Iterable +import json +import math + +from google.api_core import api_core_version +from google.protobuf import json_format +import grpc +from grpc.experimental import aio +from proto.marshal.rules import wrappers +from proto.marshal.rules.dates import DurationRule, TimestampRule +import pytest +from requests import PreparedRequest, Request, Response +from requests.sessions import Session + +try: + from google.auth.aio import credentials as ga_credentials_async + + HAS_GOOGLE_AUTH_AIO = True +except ImportError: # pragma: NO COVER + HAS_GOOGLE_AUTH_AIO = False + +from google.api_core import gapic_v1, grpc_helpers, grpc_helpers_async, path_template +from google.api_core import client_options +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +import google.auth +from google.auth import credentials as ga_credentials +from google.auth.exceptions import MutualTLSChannelError +from google.oauth2 import service_account +from google.shopping.type.types import types + +from google.shopping.merchant_products_v1.services.products_service import ( + ProductsServiceAsyncClient, + ProductsServiceClient, + pagers, + transports, +) +from google.shopping.merchant_products_v1.types import products, products_common + +CRED_INFO_JSON = { + "credential_source": "/path/to/file", + "credential_type": "service account credentials", + "principal": "service-account@example.com", +} +CRED_INFO_STRING = json.dumps(CRED_INFO_JSON) + + +async def mock_async_gen(data, chunk_size=1): + for i in range(0, len(data)): # pragma: NO COVER + chunk = data[i : i + chunk_size] + yield chunk.encode("utf-8") + + +def client_cert_source_callback(): + return b"cert bytes", b"key bytes" + + +# TODO: use async auth anon credentials by default once the minimum version of google-auth is upgraded. +# See related issue: https://github.com/googleapis/gapic-generator-python/issues/2107. +def async_anonymous_credentials(): + if HAS_GOOGLE_AUTH_AIO: + return ga_credentials_async.AnonymousCredentials() + return ga_credentials.AnonymousCredentials() + + +# If default endpoint is localhost, then default mtls endpoint will be the same. +# This method modifies the default endpoint so the client can produce a different +# mtls endpoint for endpoint testing purposes. +def modify_default_endpoint(client): + return ( + "foo.googleapis.com" + if ("localhost" in client.DEFAULT_ENDPOINT) + else client.DEFAULT_ENDPOINT + ) + + +# If default endpoint template is localhost, then default mtls endpoint will be the same. +# This method modifies the default endpoint template so the client can produce a different +# mtls endpoint for endpoint testing purposes. +def modify_default_endpoint_template(client): + return ( + "test.{UNIVERSE_DOMAIN}" + if ("localhost" in client._DEFAULT_ENDPOINT_TEMPLATE) + else client._DEFAULT_ENDPOINT_TEMPLATE + ) + + +def test__get_default_mtls_endpoint(): + api_endpoint = "example.googleapis.com" + api_mtls_endpoint = "example.mtls.googleapis.com" + sandbox_endpoint = "example.sandbox.googleapis.com" + sandbox_mtls_endpoint = "example.mtls.sandbox.googleapis.com" + non_googleapi = "api.example.com" + + assert ProductsServiceClient._get_default_mtls_endpoint(None) is None + assert ( + ProductsServiceClient._get_default_mtls_endpoint(api_endpoint) + == api_mtls_endpoint + ) + assert ( + ProductsServiceClient._get_default_mtls_endpoint(api_mtls_endpoint) + == api_mtls_endpoint + ) + assert ( + ProductsServiceClient._get_default_mtls_endpoint(sandbox_endpoint) + == sandbox_mtls_endpoint + ) + assert ( + ProductsServiceClient._get_default_mtls_endpoint(sandbox_mtls_endpoint) + == sandbox_mtls_endpoint + ) + assert ( + ProductsServiceClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi + ) + + +def test__read_environment_variables(): + assert ProductsServiceClient._read_environment_variables() == (False, "auto", None) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + assert ProductsServiceClient._read_environment_variables() == ( + True, + "auto", + None, + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): + assert ProductsServiceClient._read_environment_variables() == ( + False, + "auto", + None, + ) + + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError) as excinfo: + ProductsServiceClient._read_environment_variables() + assert ( + str(excinfo.value) + == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + assert ProductsServiceClient._read_environment_variables() == ( + False, + "never", + None, + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + assert ProductsServiceClient._read_environment_variables() == ( + False, + "always", + None, + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}): + assert ProductsServiceClient._read_environment_variables() == ( + False, + "auto", + None, + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError) as excinfo: + ProductsServiceClient._read_environment_variables() + assert ( + str(excinfo.value) + == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" + ) + + with mock.patch.dict(os.environ, {"GOOGLE_CLOUD_UNIVERSE_DOMAIN": "foo.com"}): + assert ProductsServiceClient._read_environment_variables() == ( + False, + "auto", + "foo.com", + ) + + +def test__get_client_cert_source(): + mock_provided_cert_source = mock.Mock() + mock_default_cert_source = mock.Mock() + + assert ProductsServiceClient._get_client_cert_source(None, False) is None + assert ( + ProductsServiceClient._get_client_cert_source(mock_provided_cert_source, False) + is None + ) + assert ( + ProductsServiceClient._get_client_cert_source(mock_provided_cert_source, True) + == mock_provided_cert_source + ) + + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", return_value=True + ): + with mock.patch( + "google.auth.transport.mtls.default_client_cert_source", + return_value=mock_default_cert_source, + ): + assert ( + ProductsServiceClient._get_client_cert_source(None, True) + is mock_default_cert_source + ) + assert ( + ProductsServiceClient._get_client_cert_source( + mock_provided_cert_source, "true" + ) + is mock_provided_cert_source + ) + + +@mock.patch.object( + ProductsServiceClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(ProductsServiceClient), +) +@mock.patch.object( + ProductsServiceAsyncClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(ProductsServiceAsyncClient), +) +def test__get_api_endpoint(): + api_override = "foo.com" + mock_client_cert_source = mock.Mock() + default_universe = ProductsServiceClient._DEFAULT_UNIVERSE + default_endpoint = ProductsServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=default_universe + ) + mock_universe = "bar.com" + mock_endpoint = ProductsServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=mock_universe + ) + + assert ( + ProductsServiceClient._get_api_endpoint( + api_override, mock_client_cert_source, default_universe, "always" + ) + == api_override + ) + assert ( + ProductsServiceClient._get_api_endpoint( + None, mock_client_cert_source, default_universe, "auto" + ) + == ProductsServiceClient.DEFAULT_MTLS_ENDPOINT + ) + assert ( + ProductsServiceClient._get_api_endpoint(None, None, default_universe, "auto") + == default_endpoint + ) + assert ( + ProductsServiceClient._get_api_endpoint(None, None, default_universe, "always") + == ProductsServiceClient.DEFAULT_MTLS_ENDPOINT + ) + assert ( + ProductsServiceClient._get_api_endpoint( + None, mock_client_cert_source, default_universe, "always" + ) + == ProductsServiceClient.DEFAULT_MTLS_ENDPOINT + ) + assert ( + ProductsServiceClient._get_api_endpoint(None, None, mock_universe, "never") + == mock_endpoint + ) + assert ( + ProductsServiceClient._get_api_endpoint(None, None, default_universe, "never") + == default_endpoint + ) + + with pytest.raises(MutualTLSChannelError) as excinfo: + ProductsServiceClient._get_api_endpoint( + None, mock_client_cert_source, mock_universe, "auto" + ) + assert ( + str(excinfo.value) + == "mTLS is not supported in any universe other than googleapis.com." + ) + + +def test__get_universe_domain(): + client_universe_domain = "foo.com" + universe_domain_env = "bar.com" + + assert ( + ProductsServiceClient._get_universe_domain( + client_universe_domain, universe_domain_env + ) + == client_universe_domain + ) + assert ( + ProductsServiceClient._get_universe_domain(None, universe_domain_env) + == universe_domain_env + ) + assert ( + ProductsServiceClient._get_universe_domain(None, None) + == ProductsServiceClient._DEFAULT_UNIVERSE + ) + + with pytest.raises(ValueError) as excinfo: + ProductsServiceClient._get_universe_domain("", None) + assert str(excinfo.value) == "Universe Domain cannot be an empty string." + + +@pytest.mark.parametrize( + "error_code,cred_info_json,show_cred_info", + [ + (401, CRED_INFO_JSON, True), + (403, CRED_INFO_JSON, True), + (404, CRED_INFO_JSON, True), + (500, CRED_INFO_JSON, False), + (401, None, False), + (403, None, False), + (404, None, False), + (500, None, False), + ], +) +def test__add_cred_info_for_auth_errors(error_code, cred_info_json, show_cred_info): + cred = mock.Mock(["get_cred_info"]) + cred.get_cred_info = mock.Mock(return_value=cred_info_json) + client = ProductsServiceClient(credentials=cred) + client._transport._credentials = cred + + error = core_exceptions.GoogleAPICallError("message", details=["foo"]) + error.code = error_code + + client._add_cred_info_for_auth_errors(error) + if show_cred_info: + assert error.details == ["foo", CRED_INFO_STRING] + else: + assert error.details == ["foo"] + + +@pytest.mark.parametrize("error_code", [401, 403, 404, 500]) +def test__add_cred_info_for_auth_errors_no_get_cred_info(error_code): + cred = mock.Mock([]) + assert not hasattr(cred, "get_cred_info") + client = ProductsServiceClient(credentials=cred) + client._transport._credentials = cred + + error = core_exceptions.GoogleAPICallError("message", details=[]) + error.code = error_code + + client._add_cred_info_for_auth_errors(error) + assert error.details == [] + + +@pytest.mark.parametrize( + "client_class,transport_name", + [ + (ProductsServiceClient, "grpc"), + (ProductsServiceAsyncClient, "grpc_asyncio"), + (ProductsServiceClient, "rest"), + ], +) +def test_products_service_client_from_service_account_info( + client_class, transport_name +): + creds = ga_credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_info" + ) as factory: + factory.return_value = creds + info = {"valid": True} + client = client_class.from_service_account_info(info, transport=transport_name) + assert client.transport._credentials == creds + assert isinstance(client, client_class) + + assert client.transport._host == ( + "merchantapi.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://merchantapi.googleapis.com" + ) + + +@pytest.mark.parametrize( + "transport_class,transport_name", + [ + (transports.ProductsServiceGrpcTransport, "grpc"), + (transports.ProductsServiceGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.ProductsServiceRestTransport, "rest"), + ], +) +def test_products_service_client_service_account_always_use_jwt( + transport_class, transport_name +): + with mock.patch.object( + service_account.Credentials, "with_always_use_jwt_access", create=True + ) as use_jwt: + creds = service_account.Credentials(None, None, None) + transport = transport_class(credentials=creds, always_use_jwt_access=True) + use_jwt.assert_called_once_with(True) + + with mock.patch.object( + service_account.Credentials, "with_always_use_jwt_access", create=True + ) as use_jwt: + creds = service_account.Credentials(None, None, None) + transport = transport_class(credentials=creds, always_use_jwt_access=False) + use_jwt.assert_not_called() + + +@pytest.mark.parametrize( + "client_class,transport_name", + [ + (ProductsServiceClient, "grpc"), + (ProductsServiceAsyncClient, "grpc_asyncio"), + (ProductsServiceClient, "rest"), + ], +) +def test_products_service_client_from_service_account_file( + client_class, transport_name +): + creds = ga_credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_file" + ) as factory: + factory.return_value = creds + client = client_class.from_service_account_file( + "dummy/file/path.json", transport=transport_name + ) + assert client.transport._credentials == creds + assert isinstance(client, client_class) + + client = client_class.from_service_account_json( + "dummy/file/path.json", transport=transport_name + ) + assert client.transport._credentials == creds + assert isinstance(client, client_class) + + assert client.transport._host == ( + "merchantapi.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://merchantapi.googleapis.com" + ) + + +def test_products_service_client_get_transport_class(): + transport = ProductsServiceClient.get_transport_class() + available_transports = [ + transports.ProductsServiceGrpcTransport, + transports.ProductsServiceRestTransport, + ] + assert transport in available_transports + + transport = ProductsServiceClient.get_transport_class("grpc") + assert transport == transports.ProductsServiceGrpcTransport + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (ProductsServiceClient, transports.ProductsServiceGrpcTransport, "grpc"), + ( + ProductsServiceAsyncClient, + transports.ProductsServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + (ProductsServiceClient, transports.ProductsServiceRestTransport, "rest"), + ], +) +@mock.patch.object( + ProductsServiceClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(ProductsServiceClient), +) +@mock.patch.object( + ProductsServiceAsyncClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(ProductsServiceAsyncClient), +) +def test_products_service_client_client_options( + client_class, transport_class, transport_name +): + # Check that if channel is provided we won't create a new one. + with mock.patch.object(ProductsServiceClient, "get_transport_class") as gtc: + transport = transport_class(credentials=ga_credentials.AnonymousCredentials()) + client = client_class(transport=transport) + gtc.assert_not_called() + + # Check that if channel is provided via str we will create a new one. + with mock.patch.object(ProductsServiceClient, "get_transport_class") as gtc: + client = client_class(transport=transport_name) + gtc.assert_called() + + # Check the case api_endpoint is provided. + options = client_options.ClientOptions(api_endpoint="squid.clam.whelk") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(transport=transport_name, client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host="squid.clam.whelk", + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is + # "never". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ), + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is + # "always". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_MTLS_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError) as excinfo: + client = client_class(transport=transport_name) + assert ( + str(excinfo.value) + == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" + ) + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError) as excinfo: + client = client_class(transport=transport_name) + assert ( + str(excinfo.value) + == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options, transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ), + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id="octopus", + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + # Check the case api_endpoint is provided + options = client_options.ClientOptions( + api_audience="https://language.googleapis.com" + ) + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options, transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ), + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience="https://language.googleapis.com", + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,use_client_cert_env", + [ + ( + ProductsServiceClient, + transports.ProductsServiceGrpcTransport, + "grpc", + "true", + ), + ( + ProductsServiceAsyncClient, + transports.ProductsServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + ( + ProductsServiceClient, + transports.ProductsServiceGrpcTransport, + "grpc", + "false", + ), + ( + ProductsServiceAsyncClient, + transports.ProductsServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ( + ProductsServiceClient, + transports.ProductsServiceRestTransport, + "rest", + "true", + ), + ( + ProductsServiceClient, + transports.ProductsServiceRestTransport, + "rest", + "false", + ), + ], +) +@mock.patch.object( + ProductsServiceClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(ProductsServiceClient), +) +@mock.patch.object( + ProductsServiceAsyncClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(ProductsServiceAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_products_service_client_mtls_env_auto( + client_class, transport_class, transport_name, use_client_cert_env +): + # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default + # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists. + + # Check the case client_cert_source is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + options = client_options.ClientOptions( + client_cert_source=client_cert_source_callback + ) + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options, transport=transport_name) + + if use_client_cert_env == "false": + expected_client_cert_source = None + expected_host = client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ) + else: + expected_client_cert_source = client_cert_source_callback + expected_host = client.DEFAULT_MTLS_ENDPOINT + + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + # Check the case ADC client cert is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, + ): + with mock.patch( + "google.auth.transport.mtls.default_client_cert_source", + return_value=client_cert_source_callback, + ): + if use_client_cert_env == "false": + expected_host = client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ) + expected_client_cert_source = None + else: + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_client_cert_source = client_cert_source_callback + + patched.return_value = None + client = client_class(transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + patched.return_value = None + client = client_class(transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ), + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + +@pytest.mark.parametrize( + "client_class", [ProductsServiceClient, ProductsServiceAsyncClient] +) +@mock.patch.object( + ProductsServiceClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(ProductsServiceClient), +) +@mock.patch.object( + ProductsServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(ProductsServiceAsyncClient), +) +def test_products_service_client_get_mtls_endpoint_and_cert_source(client_class): + mock_client_cert_source = mock.Mock() + + # Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "true". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + mock_api_endpoint = "foo" + options = client_options.ClientOptions( + client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint + ) + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source( + options + ) + assert api_endpoint == mock_api_endpoint + assert cert_source == mock_client_cert_source + + # Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "false". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): + mock_client_cert_source = mock.Mock() + mock_api_endpoint = "foo" + options = client_options.ClientOptions( + client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint + ) + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source( + options + ) + assert api_endpoint == mock_api_endpoint + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "never". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_ENDPOINT + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "always". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert doesn't exist. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_ENDPOINT + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert exists. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, + ): + with mock.patch( + "google.auth.transport.mtls.default_client_cert_source", + return_value=mock_client_cert_source, + ): + ( + api_endpoint, + cert_source, + ) = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT + assert cert_source == mock_client_cert_source + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError) as excinfo: + client_class.get_mtls_endpoint_and_cert_source() + + assert ( + str(excinfo.value) + == "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" + ) + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError) as excinfo: + client_class.get_mtls_endpoint_and_cert_source() + + assert ( + str(excinfo.value) + == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) + + +@pytest.mark.parametrize( + "client_class", [ProductsServiceClient, ProductsServiceAsyncClient] +) +@mock.patch.object( + ProductsServiceClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(ProductsServiceClient), +) +@mock.patch.object( + ProductsServiceAsyncClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(ProductsServiceAsyncClient), +) +def test_products_service_client_client_api_endpoint(client_class): + mock_client_cert_source = client_cert_source_callback + api_override = "foo.com" + default_universe = ProductsServiceClient._DEFAULT_UNIVERSE + default_endpoint = ProductsServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=default_universe + ) + mock_universe = "bar.com" + mock_endpoint = ProductsServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=mock_universe + ) + + # If ClientOptions.api_endpoint is set and GOOGLE_API_USE_CLIENT_CERTIFICATE="true", + # use ClientOptions.api_endpoint as the api endpoint regardless. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ): + options = client_options.ClientOptions( + client_cert_source=mock_client_cert_source, api_endpoint=api_override + ) + client = client_class( + client_options=options, + credentials=ga_credentials.AnonymousCredentials(), + ) + assert client.api_endpoint == api_override + + # If ClientOptions.api_endpoint is not set and GOOGLE_API_USE_MTLS_ENDPOINT="never", + # use the _DEFAULT_ENDPOINT_TEMPLATE populated with GDU as the api endpoint. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + client = client_class(credentials=ga_credentials.AnonymousCredentials()) + assert client.api_endpoint == default_endpoint + + # If ClientOptions.api_endpoint is not set and GOOGLE_API_USE_MTLS_ENDPOINT="always", + # use the DEFAULT_MTLS_ENDPOINT as the api endpoint. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + client = client_class(credentials=ga_credentials.AnonymousCredentials()) + assert client.api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT + + # If ClientOptions.api_endpoint is not set, GOOGLE_API_USE_MTLS_ENDPOINT="auto" (default), + # GOOGLE_API_USE_CLIENT_CERTIFICATE="false" (default), default cert source doesn't exist, + # and ClientOptions.universe_domain="bar.com", + # use the _DEFAULT_ENDPOINT_TEMPLATE populated with universe domain as the api endpoint. + options = client_options.ClientOptions() + universe_exists = hasattr(options, "universe_domain") + if universe_exists: + options = client_options.ClientOptions(universe_domain=mock_universe) + client = client_class( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + else: + client = client_class( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + assert client.api_endpoint == ( + mock_endpoint if universe_exists else default_endpoint + ) + assert client.universe_domain == ( + mock_universe if universe_exists else default_universe + ) + + # If ClientOptions does not have a universe domain attribute and GOOGLE_API_USE_MTLS_ENDPOINT="never", + # use the _DEFAULT_ENDPOINT_TEMPLATE populated with GDU as the api endpoint. + options = client_options.ClientOptions() + if hasattr(options, "universe_domain"): + delattr(options, "universe_domain") + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + client = client_class( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + assert client.api_endpoint == default_endpoint + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (ProductsServiceClient, transports.ProductsServiceGrpcTransport, "grpc"), + ( + ProductsServiceAsyncClient, + transports.ProductsServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + (ProductsServiceClient, transports.ProductsServiceRestTransport, "rest"), + ], +) +def test_products_service_client_client_options_scopes( + client_class, transport_class, transport_name +): + # Check the case scopes are provided. + options = client_options.ClientOptions( + scopes=["1", "2"], + ) + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options, transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ), + scopes=["1", "2"], + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,grpc_helpers", + [ + ( + ProductsServiceClient, + transports.ProductsServiceGrpcTransport, + "grpc", + grpc_helpers, + ), + ( + ProductsServiceAsyncClient, + transports.ProductsServiceGrpcAsyncIOTransport, + "grpc_asyncio", + grpc_helpers_async, + ), + (ProductsServiceClient, transports.ProductsServiceRestTransport, "rest", None), + ], +) +def test_products_service_client_client_options_credentials_file( + client_class, transport_class, transport_name, grpc_helpers +): + # Check the case credentials file is provided. + options = client_options.ClientOptions(credentials_file="credentials.json") + + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options, transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file="credentials.json", + host=client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ), + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + +def test_products_service_client_client_options_from_dict(): + with mock.patch( + "google.shopping.merchant_products_v1.services.products_service.transports.ProductsServiceGrpcTransport.__init__" + ) as grpc_transport: + grpc_transport.return_value = None + client = ProductsServiceClient( + client_options={"api_endpoint": "squid.clam.whelk"} + ) + grpc_transport.assert_called_once_with( + credentials=None, + credentials_file=None, + host="squid.clam.whelk", + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,grpc_helpers", + [ + ( + ProductsServiceClient, + transports.ProductsServiceGrpcTransport, + "grpc", + grpc_helpers, + ), + ( + ProductsServiceAsyncClient, + transports.ProductsServiceGrpcAsyncIOTransport, + "grpc_asyncio", + grpc_helpers_async, + ), + ], +) +def test_products_service_client_create_channel_credentials_file( + client_class, transport_class, transport_name, grpc_helpers +): + # Check the case credentials file is provided. + options = client_options.ClientOptions(credentials_file="credentials.json") + + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options, transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file="credentials.json", + host=client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ), + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + ) + + # test that the credentials from file are saved and used as the credentials. + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch.object( + google.auth, "default", autospec=True + ) as adc, mock.patch.object( + grpc_helpers, "create_channel" + ) as create_channel: + creds = ga_credentials.AnonymousCredentials() + file_creds = ga_credentials.AnonymousCredentials() + load_creds.return_value = (file_creds, None) + adc.return_value = (creds, None) + client = client_class(client_options=options, transport=transport_name) + create_channel.assert_called_with( + "merchantapi.googleapis.com:443", + credentials=file_creds, + credentials_file=None, + quota_project_id=None, + default_scopes=("https://www.googleapis.com/auth/content",), + scopes=None, + default_host="merchantapi.googleapis.com", + ssl_credentials=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + +@pytest.mark.parametrize( + "request_type", + [ + products.GetProductRequest, + dict, + ], +) +def test_get_product(request_type, transport: str = "grpc"): + client = ProductsServiceClient( + 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.get_product), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = products.Product( + name="name_value", + legacy_local=True, + offer_id="offer_id_value", + content_language="content_language_value", + feed_label="feed_label_value", + data_source="data_source_value", + version_number=1518, + ) + response = client.get_product(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + request = products.GetProductRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, products.Product) + assert response.name == "name_value" + assert response.legacy_local is True + assert response.offer_id == "offer_id_value" + assert response.content_language == "content_language_value" + assert response.feed_label == "feed_label_value" + assert response.data_source == "data_source_value" + assert response.version_number == 1518 + + +def test_get_product_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 = ProductsServiceClient( + 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 = products.GetProductRequest( + name="name_value", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_product), "__call__") as call: + call.return_value.name = ( + "foo" # operation_request.operation in compute client(s) expect a string. + ) + client.get_product(request=request) + call.assert_called() + _, args, _ = call.mock_calls[0] + assert args[0] == products.GetProductRequest( + name="name_value", + ) + + +def test_get_product_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 = ProductsServiceClient( + 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.get_product 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.get_product] = mock_rpc + request = {} + client.get_product(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.get_product(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_get_product_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 = ProductsServiceAsyncClient( + 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.get_product + 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.get_product + ] = mock_rpc + + request = {} + await client.get_product(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + await client.get_product(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_get_product_async( + transport: str = "grpc_asyncio", request_type=products.GetProductRequest +): + client = ProductsServiceAsyncClient( + 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.get_product), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + products.Product( + name="name_value", + legacy_local=True, + offer_id="offer_id_value", + content_language="content_language_value", + feed_label="feed_label_value", + data_source="data_source_value", + version_number=1518, + ) + ) + response = await client.get_product(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + request = products.GetProductRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, products.Product) + assert response.name == "name_value" + assert response.legacy_local is True + assert response.offer_id == "offer_id_value" + assert response.content_language == "content_language_value" + assert response.feed_label == "feed_label_value" + assert response.data_source == "data_source_value" + assert response.version_number == 1518 + + +@pytest.mark.asyncio +async def test_get_product_async_from_dict(): + await test_get_product_async(request_type=dict) + + +def test_get_product_field_headers(): + client = ProductsServiceClient( + 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 = products.GetProductRequest() + + request.name = "name_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_product), "__call__") as call: + call.return_value = products.Product() + client.get_product(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", + "name=name_value", + ) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_get_product_field_headers_async(): + client = ProductsServiceAsyncClient( + 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 = products.GetProductRequest() + + request.name = "name_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_product), "__call__") as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(products.Product()) + await client.get_product(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", + "name=name_value", + ) in kw["metadata"] + + +def test_get_product_flattened(): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_product), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = products.Product() + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.get_product( + name="name_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].name + mock_val = "name_value" + assert arg == mock_val + + +def test_get_product_flattened_error(): + client = ProductsServiceClient( + 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.get_product( + products.GetProductRequest(), + name="name_value", + ) + + +@pytest.mark.asyncio +async def test_get_product_flattened_async(): + client = ProductsServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_product), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = products.Product() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(products.Product()) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.get_product( + name="name_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].name + mock_val = "name_value" + assert arg == mock_val + + +@pytest.mark.asyncio +async def test_get_product_flattened_error_async(): + client = ProductsServiceAsyncClient( + 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.get_product( + products.GetProductRequest(), + name="name_value", + ) + + +@pytest.mark.parametrize( + "request_type", + [ + products.ListProductsRequest, + dict, + ], +) +def test_list_products(request_type, transport: str = "grpc"): + client = ProductsServiceClient( + 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.list_products), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = products.ListProductsResponse( + next_page_token="next_page_token_value", + ) + response = client.list_products(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + request = products.ListProductsRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListProductsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_products_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 = ProductsServiceClient( + 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 = products.ListProductsRequest( + parent="parent_value", + page_token="page_token_value", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_products), "__call__") as call: + call.return_value.name = ( + "foo" # operation_request.operation in compute client(s) expect a string. + ) + client.list_products(request=request) + call.assert_called() + _, args, _ = call.mock_calls[0] + assert args[0] == products.ListProductsRequest( + parent="parent_value", + page_token="page_token_value", + ) + + +def test_list_products_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 = ProductsServiceClient( + 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.list_products 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.list_products] = mock_rpc + request = {} + client.list_products(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.list_products(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_list_products_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 = ProductsServiceAsyncClient( + 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.list_products + 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.list_products + ] = mock_rpc + + request = {} + await client.list_products(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + await client.list_products(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_list_products_async( + transport: str = "grpc_asyncio", request_type=products.ListProductsRequest +): + client = ProductsServiceAsyncClient( + 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.list_products), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + products.ListProductsResponse( + next_page_token="next_page_token_value", + ) + ) + response = await client.list_products(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + request = products.ListProductsRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListProductsAsyncPager) + assert response.next_page_token == "next_page_token_value" + + +@pytest.mark.asyncio +async def test_list_products_async_from_dict(): + await test_list_products_async(request_type=dict) + + +def test_list_products_field_headers(): + client = ProductsServiceClient( + 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 = products.ListProductsRequest() + + request.parent = "parent_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_products), "__call__") as call: + call.return_value = products.ListProductsResponse() + client.list_products(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", + "parent=parent_value", + ) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_list_products_field_headers_async(): + client = ProductsServiceAsyncClient( + 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 = products.ListProductsRequest() + + request.parent = "parent_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_products), "__call__") as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + products.ListProductsResponse() + ) + await client.list_products(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", + "parent=parent_value", + ) in kw["metadata"] + + +def test_list_products_flattened(): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_products), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = products.ListProductsResponse() + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.list_products( + parent="parent_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].parent + mock_val = "parent_value" + assert arg == mock_val + + +def test_list_products_flattened_error(): + client = ProductsServiceClient( + 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.list_products( + products.ListProductsRequest(), + parent="parent_value", + ) + + +@pytest.mark.asyncio +async def test_list_products_flattened_async(): + client = ProductsServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_products), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = products.ListProductsResponse() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + products.ListProductsResponse() + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.list_products( + parent="parent_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].parent + mock_val = "parent_value" + assert arg == mock_val + + +@pytest.mark.asyncio +async def test_list_products_flattened_error_async(): + client = ProductsServiceAsyncClient( + 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.list_products( + products.ListProductsRequest(), + parent="parent_value", + ) + + +def test_list_products_pager(transport_name: str = "grpc"): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport_name, + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_products), "__call__") as call: + # Set the response to a series of pages. + call.side_effect = ( + products.ListProductsResponse( + products=[ + products.Product(), + products.Product(), + products.Product(), + ], + next_page_token="abc", + ), + products.ListProductsResponse( + products=[], + next_page_token="def", + ), + products.ListProductsResponse( + products=[ + products.Product(), + ], + next_page_token="ghi", + ), + products.ListProductsResponse( + products=[ + products.Product(), + products.Product(), + ], + ), + RuntimeError, + ) + + expected_metadata = () + retry = retries.Retry() + timeout = 5 + expected_metadata = tuple(expected_metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", ""),)), + ) + pager = client.list_products(request={}, retry=retry, timeout=timeout) + + assert pager._metadata == expected_metadata + assert pager._retry == retry + assert pager._timeout == timeout + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, products.Product) for i in results) + + +def test_list_products_pages(transport_name: str = "grpc"): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport_name, + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_products), "__call__") as call: + # Set the response to a series of pages. + call.side_effect = ( + products.ListProductsResponse( + products=[ + products.Product(), + products.Product(), + products.Product(), + ], + next_page_token="abc", + ), + products.ListProductsResponse( + products=[], + next_page_token="def", + ), + products.ListProductsResponse( + products=[ + products.Product(), + ], + next_page_token="ghi", + ), + products.ListProductsResponse( + products=[ + products.Product(), + products.Product(), + ], + ), + RuntimeError, + ) + pages = list(client.list_products(request={}).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.asyncio +async def test_list_products_async_pager(): + client = ProductsServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_products), "__call__", new_callable=mock.AsyncMock + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + products.ListProductsResponse( + products=[ + products.Product(), + products.Product(), + products.Product(), + ], + next_page_token="abc", + ), + products.ListProductsResponse( + products=[], + next_page_token="def", + ), + products.ListProductsResponse( + products=[ + products.Product(), + ], + next_page_token="ghi", + ), + products.ListProductsResponse( + products=[ + products.Product(), + products.Product(), + ], + ), + RuntimeError, + ) + async_pager = await client.list_products( + request={}, + ) + assert async_pager.next_page_token == "abc" + responses = [] + async for response in async_pager: # pragma: no branch + responses.append(response) + + assert len(responses) == 6 + assert all(isinstance(i, products.Product) for i in responses) + + +@pytest.mark.asyncio +async def test_list_products_async_pages(): + client = ProductsServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_products), "__call__", new_callable=mock.AsyncMock + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + products.ListProductsResponse( + products=[ + products.Product(), + products.Product(), + products.Product(), + ], + next_page_token="abc", + ), + products.ListProductsResponse( + products=[], + next_page_token="def", + ), + products.ListProductsResponse( + products=[ + products.Product(), + ], + next_page_token="ghi", + ), + products.ListProductsResponse( + products=[ + products.Product(), + products.Product(), + ], + ), + RuntimeError, + ) + pages = [] + # Workaround issue in python 3.9 related to code coverage by adding `# pragma: no branch` + # See https://github.com/googleapis/gapic-generator-python/pull/1174#issuecomment-1025132372 + async for page_ in ( # pragma: no branch + await client.list_products(request={}) + ).pages: + pages.append(page_) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +def test_get_product_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 = ProductsServiceClient( + 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.get_product 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.get_product] = mock_rpc + + request = {} + client.get_product(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.get_product(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_get_product_rest_required_fields(request_type=products.GetProductRequest): + transport_class = transports.ProductsServiceRestTransport + + request_init = {} + request_init["name"] = "" + 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() + ).get_product._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["name"] = "name_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_product._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = products.Product() + # 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": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + # Convert return value to protobuf type + return_value = products.Product.pb(return_value) + 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.get_product(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_product_rest_unset_required_fields(): + transport = transports.ProductsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_product._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +def test_get_product_rest_flattened(): + client = ProductsServiceClient( + 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 = products.Product() + + # get arguments that satisfy an http rule for this method + sample_request = {"name": "accounts/sample1/products/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + # Convert return value to protobuf type + return_value = products.Product.pb(return_value) + 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.get_product(**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/products/v1/{name=accounts/*/products/*}" % client.transport._host, + args[1], + ) + + +def test_get_product_rest_flattened_error(transport: str = "rest"): + client = ProductsServiceClient( + 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.get_product( + products.GetProductRequest(), + name="name_value", + ) + + +def test_list_products_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 = ProductsServiceClient( + 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.list_products 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.list_products] = mock_rpc + + request = {} + client.list_products(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.list_products(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_list_products_rest_required_fields(request_type=products.ListProductsRequest): + transport_class = transports.ProductsServiceRestTransport + + request_init = {} + request_init["parent"] = "" + 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() + ).list_products._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["parent"] = "parent_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_products._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set( + ( + "page_size", + "page_token", + ) + ) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "parent" in jsonified_request + assert jsonified_request["parent"] == "parent_value" + + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = products.ListProductsResponse() + # 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": "get", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + # Convert return value to protobuf type + return_value = products.ListProductsResponse.pb(return_value) + 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.list_products(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_products_rest_unset_required_fields(): + transport = transports.ProductsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_products._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +def test_list_products_rest_flattened(): + client = ProductsServiceClient( + 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 = products.ListProductsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "accounts/sample1"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + # Convert return value to protobuf type + return_value = products.ListProductsResponse.pb(return_value) + 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.list_products(**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/products/v1/{parent=accounts/*}/products" % client.transport._host, + args[1], + ) + + +def test_list_products_rest_flattened_error(transport: str = "rest"): + client = ProductsServiceClient( + 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.list_products( + products.ListProductsRequest(), + parent="parent_value", + ) + + +def test_list_products_rest_pager(transport: str = "rest"): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # TODO(kbandes): remove this mock unless there's a good reason for it. + # with mock.patch.object(path_template, 'transcode') as transcode: + # Set the response as a series of pages + response = ( + products.ListProductsResponse( + products=[ + products.Product(), + products.Product(), + products.Product(), + ], + next_page_token="abc", + ), + products.ListProductsResponse( + products=[], + next_page_token="def", + ), + products.ListProductsResponse( + products=[ + products.Product(), + ], + next_page_token="ghi", + ), + products.ListProductsResponse( + products=[ + products.Product(), + products.Product(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(products.ListProductsResponse.to_json(x) for x in response) + return_values = tuple(Response() for i in response) + for return_val, response_val in zip(return_values, response): + return_val._content = response_val.encode("UTF-8") + return_val.status_code = 200 + req.side_effect = return_values + + sample_request = {"parent": "accounts/sample1"} + + pager = client.list_products(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, products.Product) for i in results) + + pages = list(client.list_products(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.ProductsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.ProductsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = ProductsServiceClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.ProductsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = ProductsServiceClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = ProductsServiceClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.ProductsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = ProductsServiceClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.ProductsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = ProductsServiceClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.ProductsServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.ProductsServiceGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.ProductsServiceGrpcTransport, + transports.ProductsServiceGrpcAsyncIOTransport, + transports.ProductsServiceRestTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +def test_transport_kind_grpc(): + transport = ProductsServiceClient.get_transport_class("grpc")( + credentials=ga_credentials.AnonymousCredentials() + ) + assert transport.kind == "grpc" + + +def test_initialize_client_w_grpc(): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="grpc" + ) + assert client is not None + + +# 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_get_product_empty_call_grpc(): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object(type(client.transport.get_product), "__call__") as call: + call.return_value = products.Product() + client.get_product(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = products.GetProductRequest() + + 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_list_products_empty_call_grpc(): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object(type(client.transport.list_products), "__call__") as call: + call.return_value = products.ListProductsResponse() + client.list_products(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = products.ListProductsRequest() + + assert args[0] == request_msg + + +def test_transport_kind_grpc_asyncio(): + transport = ProductsServiceAsyncClient.get_transport_class("grpc_asyncio")( + credentials=async_anonymous_credentials() + ) + assert transport.kind == "grpc_asyncio" + + +def test_initialize_client_w_grpc_asyncio(): + client = ProductsServiceAsyncClient( + credentials=async_anonymous_credentials(), transport="grpc_asyncio" + ) + assert client is not None + + +# 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_get_product_empty_call_grpc_asyncio(): + client = ProductsServiceAsyncClient( + credentials=async_anonymous_credentials(), + transport="grpc_asyncio", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object(type(client.transport.get_product), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + products.Product( + name="name_value", + legacy_local=True, + offer_id="offer_id_value", + content_language="content_language_value", + feed_label="feed_label_value", + data_source="data_source_value", + version_number=1518, + ) + ) + await client.get_product(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = products.GetProductRequest() + + 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_list_products_empty_call_grpc_asyncio(): + client = ProductsServiceAsyncClient( + credentials=async_anonymous_credentials(), + transport="grpc_asyncio", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object(type(client.transport.list_products), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + products.ListProductsResponse( + next_page_token="next_page_token_value", + ) + ) + await client.list_products(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = products.ListProductsRequest() + + assert args[0] == request_msg + + +def test_transport_kind_rest(): + transport = ProductsServiceClient.get_transport_class("rest")( + credentials=ga_credentials.AnonymousCredentials() + ) + assert transport.kind == "rest" + + +def test_get_product_rest_bad_request(request_type=products.GetProductRequest): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + # send a request that will satisfy transcoding + request_init = {"name": "accounts/sample1/products/sample2"} + 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.get_product(request) + + +@pytest.mark.parametrize( + "request_type", + [ + products.GetProductRequest, + dict, + ], +) +def test_get_product_rest_call_success(request_type): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + # send a request that will satisfy transcoding + request_init = {"name": "accounts/sample1/products/sample2"} + 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 = products.Product( + name="name_value", + legacy_local=True, + offer_id="offer_id_value", + content_language="content_language_value", + feed_label="feed_label_value", + data_source="data_source_value", + version_number=1518, + ) + + # Wrap the value into a proper Response obj + response_value = mock.Mock() + response_value.status_code = 200 + + # Convert return value to protobuf type + return_value = products.Product.pb(return_value) + 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.get_product(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, products.Product) + assert response.name == "name_value" + assert response.legacy_local is True + assert response.offer_id == "offer_id_value" + assert response.content_language == "content_language_value" + assert response.feed_label == "feed_label_value" + assert response.data_source == "data_source_value" + assert response.version_number == 1518 + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_product_rest_interceptors(null_interceptor): + transport = transports.ProductsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ProductsServiceRestInterceptor(), + ) + client = ProductsServiceClient(transport=transport) + + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ProductsServiceRestInterceptor, "post_get_product" + ) as post, mock.patch.object( + transports.ProductsServiceRestInterceptor, "post_get_product_with_metadata" + ) as post_with_metadata, mock.patch.object( + transports.ProductsServiceRestInterceptor, "pre_get_product" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + post_with_metadata.assert_not_called() + pb_message = products.GetProductRequest.pb(products.GetProductRequest()) + 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 = products.Product.to_json(products.Product()) + req.return_value.content = return_value + + request = products.GetProductRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = products.Product() + post_with_metadata.return_value = products.Product(), metadata + + client.get_product( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + post_with_metadata.assert_called_once() + + +def test_list_products_rest_bad_request(request_type=products.ListProductsRequest): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + # send a request that will satisfy transcoding + request_init = {"parent": "accounts/sample1"} + 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.list_products(request) + + +@pytest.mark.parametrize( + "request_type", + [ + products.ListProductsRequest, + dict, + ], +) +def test_list_products_rest_call_success(request_type): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "accounts/sample1"} + 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 = products.ListProductsResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = mock.Mock() + response_value.status_code = 200 + + # Convert return value to protobuf type + return_value = products.ListProductsResponse.pb(return_value) + 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.list_products(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListProductsPager) + assert response.next_page_token == "next_page_token_value" + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_products_rest_interceptors(null_interceptor): + transport = transports.ProductsServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ProductsServiceRestInterceptor(), + ) + client = ProductsServiceClient(transport=transport) + + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ProductsServiceRestInterceptor, "post_list_products" + ) as post, mock.patch.object( + transports.ProductsServiceRestInterceptor, "post_list_products_with_metadata" + ) as post_with_metadata, mock.patch.object( + transports.ProductsServiceRestInterceptor, "pre_list_products" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + post_with_metadata.assert_not_called() + pb_message = products.ListProductsRequest.pb(products.ListProductsRequest()) + 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 = products.ListProductsResponse.to_json( + products.ListProductsResponse() + ) + req.return_value.content = return_value + + request = products.ListProductsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = products.ListProductsResponse() + post_with_metadata.return_value = products.ListProductsResponse(), metadata + + client.list_products( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + post_with_metadata.assert_called_once() + + +def test_initialize_client_w_rest(): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + assert client is not None + + +# 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_get_product_empty_call_rest(): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object(type(client.transport.get_product), "__call__") as call: + client.get_product(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = products.GetProductRequest() + + 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_list_products_empty_call_rest(): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object(type(client.transport.list_products), "__call__") as call: + client.list_products(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = products.ListProductsRequest() + + assert args[0] == request_msg + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.ProductsServiceGrpcTransport, + ) + + +def test_products_service_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.ProductsServiceTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_products_service_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.shopping.merchant_products_v1.services.products_service.transports.ProductsServiceTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.ProductsServiceTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "get_product", + "list_products", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + with pytest.raises(NotImplementedError): + transport.close() + + # Catch all for all remaining methods and properties + remainder = [ + "kind", + ] + for r in remainder: + with pytest.raises(NotImplementedError): + getattr(transport, r)() + + +def test_products_service_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch( + "google.shopping.merchant_products_v1.services.products_service.transports.ProductsServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.ProductsServiceTransport( + credentials_file="credentials.json", + quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=None, + default_scopes=("https://www.googleapis.com/auth/content",), + quota_project_id="octopus", + ) + + +def test_products_service_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(google.auth, "default", autospec=True) as adc, mock.patch( + "google.shopping.merchant_products_v1.services.products_service.transports.ProductsServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.ProductsServiceTransport() + adc.assert_called_once() + + +def test_products_service_auth_adc(): + # If no credentials are provided, we should use ADC credentials. + with mock.patch.object(google.auth, "default", autospec=True) as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + ProductsServiceClient() + adc.assert_called_once_with( + scopes=None, + default_scopes=("https://www.googleapis.com/auth/content",), + quota_project_id=None, + ) + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.ProductsServiceGrpcTransport, + transports.ProductsServiceGrpcAsyncIOTransport, + ], +) +def test_products_service_transport_auth_adc(transport_class): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object(google.auth, "default", autospec=True) as adc: + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport_class(quota_project_id="octopus", scopes=["1", "2"]) + adc.assert_called_once_with( + scopes=["1", "2"], + default_scopes=("https://www.googleapis.com/auth/content",), + quota_project_id="octopus", + ) + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.ProductsServiceGrpcTransport, + transports.ProductsServiceGrpcAsyncIOTransport, + transports.ProductsServiceRestTransport, + ], +) +def test_products_service_transport_auth_gdch_credentials(transport_class): + host = "https://language.com" + api_audience_tests = [None, "https://language2.com"] + api_audience_expect = [host, "https://language2.com"] + for t, e in zip(api_audience_tests, api_audience_expect): + with mock.patch.object(google.auth, "default", autospec=True) as adc: + gdch_mock = mock.MagicMock() + type(gdch_mock).with_gdch_audience = mock.PropertyMock( + return_value=gdch_mock + ) + adc.return_value = (gdch_mock, None) + transport_class(host=host, api_audience=t) + gdch_mock.with_gdch_audience.assert_called_once_with(e) + + +@pytest.mark.parametrize( + "transport_class,grpc_helpers", + [ + (transports.ProductsServiceGrpcTransport, grpc_helpers), + (transports.ProductsServiceGrpcAsyncIOTransport, grpc_helpers_async), + ], +) +def test_products_service_transport_create_channel(transport_class, grpc_helpers): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object( + google.auth, "default", autospec=True + ) as adc, mock.patch.object( + grpc_helpers, "create_channel", autospec=True + ) as create_channel: + creds = ga_credentials.AnonymousCredentials() + adc.return_value = (creds, None) + transport_class(quota_project_id="octopus", scopes=["1", "2"]) + + create_channel.assert_called_with( + "merchantapi.googleapis.com:443", + credentials=creds, + credentials_file=None, + quota_project_id="octopus", + default_scopes=("https://www.googleapis.com/auth/content",), + scopes=["1", "2"], + default_host="merchantapi.googleapis.com", + ssl_credentials=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.ProductsServiceGrpcTransport, + transports.ProductsServiceGrpcAsyncIOTransport, + ], +) +def test_products_service_grpc_transport_client_cert_source_for_mtls(transport_class): + cred = ga_credentials.AnonymousCredentials() + + # Check ssl_channel_credentials is used if provided. + with mock.patch.object(transport_class, "create_channel") as mock_create_channel: + mock_ssl_channel_creds = mock.Mock() + transport_class( + host="squid.clam.whelk", + credentials=cred, + ssl_channel_credentials=mock_ssl_channel_creds, + ) + mock_create_channel.assert_called_once_with( + "squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=None, + ssl_credentials=mock_ssl_channel_creds, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + # Check if ssl_channel_credentials is not provided, then client_cert_source_for_mtls + # is used. + with mock.patch.object(transport_class, "create_channel", return_value=mock.Mock()): + with mock.patch("grpc.ssl_channel_credentials") as mock_ssl_cred: + transport_class( + credentials=cred, + client_cert_source_for_mtls=client_cert_source_callback, + ) + expected_cert, expected_key = client_cert_source_callback() + mock_ssl_cred.assert_called_once_with( + certificate_chain=expected_cert, private_key=expected_key + ) + + +def test_products_service_http_transport_client_cert_source_for_mtls(): + cred = ga_credentials.AnonymousCredentials() + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel" + ) as mock_configure_mtls_channel: + transports.ProductsServiceRestTransport( + credentials=cred, client_cert_source_for_mtls=client_cert_source_callback + ) + mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback) + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "grpc_asyncio", + "rest", + ], +) +def test_products_service_host_no_port(transport_name): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions( + api_endpoint="merchantapi.googleapis.com" + ), + transport=transport_name, + ) + assert client.transport._host == ( + "merchantapi.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://merchantapi.googleapis.com" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "grpc_asyncio", + "rest", + ], +) +def test_products_service_host_with_port(transport_name): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions( + api_endpoint="merchantapi.googleapis.com:8000" + ), + transport=transport_name, + ) + assert client.transport._host == ( + "merchantapi.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://merchantapi.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_products_service_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = ProductsServiceClient( + credentials=creds1, + transport=transport_name, + ) + client2 = ProductsServiceClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.get_product._session + session2 = client2.transport.get_product._session + assert session1 != session2 + session1 = client1.transport.list_products._session + session2 = client2.transport.list_products._session + assert session1 != session2 + + +def test_products_service_grpc_transport_channel(): + channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials()) + + # Check that channel is used if provided. + transport = transports.ProductsServiceGrpcTransport( + host="squid.clam.whelk", + channel=channel, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + assert transport._ssl_channel_credentials == None + + +def test_products_service_grpc_asyncio_transport_channel(): + channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials()) + + # Check that channel is used if provided. + transport = transports.ProductsServiceGrpcAsyncIOTransport( + host="squid.clam.whelk", + channel=channel, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + assert transport._ssl_channel_credentials == None + + +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. +@pytest.mark.parametrize( + "transport_class", + [ + transports.ProductsServiceGrpcTransport, + transports.ProductsServiceGrpcAsyncIOTransport, + ], +) +def test_products_service_transport_channel_mtls_with_client_cert_source( + transport_class, +): + with mock.patch( + "grpc.ssl_channel_credentials", autospec=True + ) as grpc_ssl_channel_cred: + with mock.patch.object( + transport_class, "create_channel" + ) as grpc_create_channel: + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + cred = ga_credentials.AnonymousCredentials() + with pytest.warns(DeprecationWarning): + with mock.patch.object(google.auth, "default") as adc: + adc.return_value = (cred, None) + transport = transport_class( + host="squid.clam.whelk", + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + adc.assert_called_once() + + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=None, + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + assert transport.grpc_channel == mock_grpc_channel + assert transport._ssl_channel_credentials == mock_ssl_cred + + +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. +@pytest.mark.parametrize( + "transport_class", + [ + transports.ProductsServiceGrpcTransport, + transports.ProductsServiceGrpcAsyncIOTransport, + ], +) +def test_products_service_transport_channel_mtls_with_adc(transport_class): + mock_ssl_cred = mock.Mock() + with mock.patch.multiple( + "google.auth.transport.grpc.SslCredentials", + __init__=mock.Mock(return_value=None), + ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), + ): + with mock.patch.object( + transport_class, "create_channel" + ) as grpc_create_channel: + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + mock_cred = mock.Mock() + + with pytest.warns(DeprecationWarning): + transport = transport_class( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=None, + ) + + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + credentials_file=None, + scopes=None, + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + assert transport.grpc_channel == mock_grpc_channel + + +def test_product_path(): + account = "squid" + product = "clam" + expected = "accounts/{account}/products/{product}".format( + account=account, + product=product, + ) + actual = ProductsServiceClient.product_path(account, product) + assert expected == actual + + +def test_parse_product_path(): + expected = { + "account": "whelk", + "product": "octopus", + } + path = ProductsServiceClient.product_path(**expected) + + # Check that the path construction is reversible. + actual = ProductsServiceClient.parse_product_path(path) + assert expected == actual + + +def test_common_billing_account_path(): + billing_account = "oyster" + expected = "billingAccounts/{billing_account}".format( + billing_account=billing_account, + ) + actual = ProductsServiceClient.common_billing_account_path(billing_account) + assert expected == actual + + +def test_parse_common_billing_account_path(): + expected = { + "billing_account": "nudibranch", + } + path = ProductsServiceClient.common_billing_account_path(**expected) + + # Check that the path construction is reversible. + actual = ProductsServiceClient.parse_common_billing_account_path(path) + assert expected == actual + + +def test_common_folder_path(): + folder = "cuttlefish" + expected = "folders/{folder}".format( + folder=folder, + ) + actual = ProductsServiceClient.common_folder_path(folder) + assert expected == actual + + +def test_parse_common_folder_path(): + expected = { + "folder": "mussel", + } + path = ProductsServiceClient.common_folder_path(**expected) + + # Check that the path construction is reversible. + actual = ProductsServiceClient.parse_common_folder_path(path) + assert expected == actual + + +def test_common_organization_path(): + organization = "winkle" + expected = "organizations/{organization}".format( + organization=organization, + ) + actual = ProductsServiceClient.common_organization_path(organization) + assert expected == actual + + +def test_parse_common_organization_path(): + expected = { + "organization": "nautilus", + } + path = ProductsServiceClient.common_organization_path(**expected) + + # Check that the path construction is reversible. + actual = ProductsServiceClient.parse_common_organization_path(path) + assert expected == actual + + +def test_common_project_path(): + project = "scallop" + expected = "projects/{project}".format( + project=project, + ) + actual = ProductsServiceClient.common_project_path(project) + assert expected == actual + + +def test_parse_common_project_path(): + expected = { + "project": "abalone", + } + path = ProductsServiceClient.common_project_path(**expected) + + # Check that the path construction is reversible. + actual = ProductsServiceClient.parse_common_project_path(path) + assert expected == actual + + +def test_common_location_path(): + project = "squid" + location = "clam" + expected = "projects/{project}/locations/{location}".format( + project=project, + location=location, + ) + actual = ProductsServiceClient.common_location_path(project, location) + assert expected == actual + + +def test_parse_common_location_path(): + expected = { + "project": "whelk", + "location": "octopus", + } + path = ProductsServiceClient.common_location_path(**expected) + + # Check that the path construction is reversible. + actual = ProductsServiceClient.parse_common_location_path(path) + assert expected == actual + + +def test_client_with_default_client_info(): + client_info = gapic_v1.client_info.ClientInfo() + + with mock.patch.object( + transports.ProductsServiceTransport, "_prep_wrapped_messages" + ) as prep: + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + with mock.patch.object( + transports.ProductsServiceTransport, "_prep_wrapped_messages" + ) as prep: + transport_class = ProductsServiceClient.get_transport_class() + transport = transport_class( + credentials=ga_credentials.AnonymousCredentials(), + client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + +def test_transport_close_grpc(): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="grpc" + ) + with mock.patch.object( + type(getattr(client.transport, "_grpc_channel")), "close" + ) as close: + with client: + close.assert_not_called() + close.assert_called_once() + + +@pytest.mark.asyncio +async def test_transport_close_grpc_asyncio(): + client = ProductsServiceAsyncClient( + credentials=async_anonymous_credentials(), transport="grpc_asyncio" + ) + with mock.patch.object( + type(getattr(client.transport, "_grpc_channel")), "close" + ) as close: + async with client: + close.assert_not_called() + close.assert_called_once() + + +def test_transport_close_rest(): + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + with mock.patch.object( + type(getattr(client.transport, "_session")), "close" + ) as close: + with client: + close.assert_not_called() + close.assert_called_once() + + +def test_client_ctx(): + transports = [ + "rest", + "grpc", + ] + for transport in transports: + client = ProductsServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport=transport + ) + # Test client calls underlying transport. + with mock.patch.object(type(client.transport), "close") as close: + close.assert_not_called() + with client: + pass + close.assert_called() + + +@pytest.mark.parametrize( + "client_class,transport_class", + [ + (ProductsServiceClient, transports.ProductsServiceGrpcTransport), + (ProductsServiceAsyncClient, transports.ProductsServiceGrpcAsyncIOTransport), + ], +) +def test_api_key_credentials(client_class, transport_class): + with mock.patch.object( + google.auth._default, "get_api_key_credentials", create=True + ) as get_api_key_credentials: + mock_cred = mock.Mock() + get_api_key_credentials.return_value = mock_cred + options = client_options.ClientOptions() + options.api_key = "api_key" + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=mock_cred, + credentials_file=None, + host=client._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=client._DEFAULT_UNIVERSE + ), + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + api_audience=None, + )