diff --git a/.librarian/generator-input/client-post-processing/add-dependency-google-cloud-common.yaml b/.librarian/generator-input/client-post-processing/add-dependency-google-cloud-common.yaml index c01f2e3ec861..036cb875be21 100644 --- a/.librarian/generator-input/client-post-processing/add-dependency-google-cloud-common.yaml +++ b/.librarian/generator-input/client-post-processing/add-dependency-google-cloud-common.yaml @@ -19,14 +19,14 @@ replacements: ] before: | dependencies = \[ - "google-api-core\[grpc\] >= 2.24.2, <3.0.0", + "google-api-core\[grpc\] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", "grpcio >= 1.59.0, < 2.0.0", after: | dependencies = [ - "google-api-core[grpc] >= 2.24.2, <3.0.0", + "google-api-core[grpc] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", diff --git a/.librarian/generator-input/client-post-processing/add-missing-dependencies-to-setup-py-constraints.yaml b/.librarian/generator-input/client-post-processing/add-missing-dependencies-to-setup-py-constraints.yaml index 5c80b764aa9b..11644256a0ae 100644 --- a/.librarian/generator-input/client-post-processing/add-missing-dependencies-to-setup-py-constraints.yaml +++ b/.librarian/generator-input/client-post-processing/add-missing-dependencies-to-setup-py-constraints.yaml @@ -19,14 +19,14 @@ replacements: ] before: | dependencies = \[ - "google-api-core\[grpc\] >= 2.24.2, <3.0.0", + "google-api-core\[grpc\] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", "grpcio >= 1.59.0, < 2.0.0", after: | dependencies = [ - "google-api-core[grpc] >= 2.24.2, <3.0.0", + "google-api-core[grpc] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", @@ -38,14 +38,14 @@ replacements: ] before: | dependencies = \[ - "google-api-core\[grpc\] >= 2.24.2, <3.0.0", + "google-api-core\[grpc\] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", "grpcio >= 1.59.0, < 2.0.0", after: | dependencies = [ - "google-api-core[grpc] >= 2.24.2, <3.0.0", + "google-api-core[grpc] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", @@ -57,14 +57,14 @@ replacements: ] before: | dependencies = \[ - "google-api-core\[grpc\] >= 2.24.2, <3.0.0", + "google-api-core\[grpc\] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", "grpcio >= 1.59.0, < 2.0.0", after: | dependencies = [ - "google-api-core[grpc] >= 2.24.2, <3.0.0", + "google-api-core[grpc] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", @@ -76,14 +76,14 @@ replacements: ] before: | dependencies = \[ - "google-api-core\[grpc\] >= 2.24.2, <3.0.0", + "google-api-core\[grpc\] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", "grpcio >= 1.59.0, < 2.0.0", after: | dependencies = [ - "google-api-core[grpc] >= 2.24.2, <3.0.0", + "google-api-core[grpc] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", @@ -95,14 +95,14 @@ replacements: ] before: | dependencies = \[ - "google-api-core\[grpc\] >= 2.24.2, <3.0.0", + "google-api-core\[grpc\] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", "grpcio >= 1.59.0, < 2.0.0", after: | dependencies = [ - "google-api-core[grpc] >= 2.24.2, <3.0.0", + "google-api-core[grpc] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", @@ -114,14 +114,14 @@ replacements: ] before: | dependencies = \[ - "google-api-core\[grpc\] >= 2.24.2, <3.0.0", + "google-api-core\[grpc\] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", "grpcio >= 1.59.0, < 2.0.0", after: | dependencies = [ - "google-api-core[grpc] >= 2.24.2, <3.0.0", + "google-api-core[grpc] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", diff --git a/.librarian/generator-input/client-post-processing/asset-integration.yaml b/.librarian/generator-input/client-post-processing/asset-integration.yaml index 5a6eb97a18c4..42411db6c8ef 100644 --- a/.librarian/generator-input/client-post-processing/asset-integration.yaml +++ b/.librarian/generator-input/client-post-processing/asset-integration.yaml @@ -19,14 +19,14 @@ replacements: ] before: | dependencies = \[ - "google-api-core\[grpc\] >= 2.24.2, <3.0.0", + "google-api-core\[grpc\] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", "grpcio >= 1.59.0, < 2.0.0", after: | dependencies = [ - "google-api-core[grpc] >= 2.24.2, <3.0.0", + "google-api-core[grpc] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", diff --git a/.librarian/generator-input/client-post-processing/bigtable-integration.yaml b/.librarian/generator-input/client-post-processing/bigtable-integration.yaml index fb373802cddc..6ff2dfb6ad8b 100644 --- a/.librarian/generator-input/client-post-processing/bigtable-integration.yaml +++ b/.librarian/generator-input/client-post-processing/bigtable-integration.yaml @@ -385,6 +385,7 @@ replacements: "3.12", "3.13", "3.14", + "3.15", ] UNIT_TEST_STANDARD_DEPENDENCIES = [ @@ -418,6 +419,16 @@ replacements: SYSTEM_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = {} CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() + # Path to the centralized mypy configuration file at the repository root. + # Search upwards to support running nox from both monorepo packages and integration test goldens. + MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), + ) # 'docfx' is excluded since it only needs to run in 'docs-presubmit' nox.options.sessions = [ @@ -527,7 +538,12 @@ replacements: "types-requests", ) session.install("google-cloud-testutils") - session.run("mypy", "-p", "google.cloud.bigtable.data") + session.run( + "mypy", + f"--config-file={MYPY_CONFIG_FILE}", + "-p", + "google.cloud.bigtable.data", + ) @nox.session(python=DEFAULT_PYTHON_VERSION) @@ -568,25 +584,21 @@ replacements: @nox.session(python=ALL_PYTHON) @nox.parametrize( "protobuf_implementation", - ["python", "upb", "cpp"], + ["python", "upb"], ) def unit(session, protobuf_implementation): # Install all test dependencies, then install this package in-place. - py_version = tuple([int(v) for v in session.python.split(".")]) - if protobuf_implementation == "cpp" and py_version >= (3, 11): - session.skip("cpp implementation is not supported in python 3.11+") + + # TODO(https://github.com/googleapis/google-cloud-python/issues/17741): + # Remove once `google-crc32c` wheels are published for 3.15 + if session.python == "3.15": + session.skip("Skipping 3.15 until wheels are available for google-crc32c.") constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" ) install_unittest_dependencies(session, "-c", constraints_path) - # TODO(https://github.com/googleapis/synthtool/issues/1976): - # Remove the 'cpp' implementation once support for Protobuf 3.x is dropped. - # The 'cpp' implementation requires Protobuf<4. - if protobuf_implementation == "cpp": - session.install("protobuf<4") - # Run py.test against the unit tests. session.run( "py.test", @@ -818,15 +830,11 @@ replacements: @nox.session(python=DEFAULT_PYTHON_VERSION) @nox.parametrize( "protobuf_implementation", - ["python", "upb", "cpp"], + ["python", "upb"], ) def prerelease_deps(session, protobuf_implementation): """Run all tests with prerelease versions of dependencies installed.""" - py_version = tuple([int(v) for v in session.python.split(".")]) - if protobuf_implementation == "cpp" and py_version >= (3, 11): - session.skip("cpp implementation is not supported in python 3.11+") - # Install all dependencies session.install("-e", ".[all, tests, tracing]") unit_deps_all = UNIT_TEST_STANDARD_DEPENDENCIES + UNIT_TEST_EXTERNAL_DEPENDENCIES diff --git a/.librarian/generator-input/client-post-processing/containeranalysis-grafeas-integration.yaml b/.librarian/generator-input/client-post-processing/containeranalysis-grafeas-integration.yaml index 6f8fe28e7966..880f20cc44fa 100644 --- a/.librarian/generator-input/client-post-processing/containeranalysis-grafeas-integration.yaml +++ b/.librarian/generator-input/client-post-processing/containeranalysis-grafeas-integration.yaml @@ -19,14 +19,14 @@ replacements: ] before: | dependencies = \[ - "google-api-core\[grpc\] >= 2.24.2, <3.0.0", + "google-api-core\[grpc\] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", "grpcio >= 1.59.0, < 2.0.0", after: | dependencies = [ - "google-api-core[grpc] >= 2.24.2, <3.0.0", + "google-api-core[grpc] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", diff --git a/.librarian/generator-input/client-post-processing/integrate-isolated-handwritten-code.yaml b/.librarian/generator-input/client-post-processing/integrate-isolated-handwritten-code.yaml index fb4b1af44058..0a1aa051d9ae 100644 --- a/.librarian/generator-input/client-post-processing/integrate-isolated-handwritten-code.yaml +++ b/.librarian/generator-input/client-post-processing/integrate-isolated-handwritten-code.yaml @@ -60,14 +60,14 @@ replacements: ] before: | dependencies = \[ - "google-api-core\[grpc\] >= 2.24.2, <3.0.0", + "google-api-core\[grpc\] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", "grpcio >= 1.59.0, < 2.0.0", after: | dependencies = [ - "google-api-core[grpc] >= 2.24.2, <3.0.0", + "google-api-core[grpc] >= 2.25.0, <3.0.0", # Exclude incompatible versions of `google-auth` # See https://github.com/googleapis/google-cloud-python/issues/12364 "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", diff --git a/.librarian/generator-input/client-post-processing/logging-integration.yaml b/.librarian/generator-input/client-post-processing/logging-integration.yaml index f5ff09d4d7d5..e7141bb85c20 100644 --- a/.librarian/generator-input/client-post-processing/logging-integration.yaml +++ b/.librarian/generator-input/client-post-processing/logging-integration.yaml @@ -328,14 +328,14 @@ replacements: ------------------- .. toctree:: :maxdepth: 2 - + usage - + Documentation ------------------- .. toctree:: :maxdepth: 3 - + client logger entries @@ -344,20 +344,20 @@ replacements: sink handlers transport - - + + Migration Guides ---------------- - + See the guide below for instructions on migrating between major releases of this library. - + .. toctree:: :maxdepth: 2 - + UPGRADING count: 1 - paths: [ - "packages/google-cloud-logging/google/cloud/logging/__init__.py", + "packages/google-cloud-logging/google/cloud/logging/__init__.py", ] before: | from google.cloud.logging import gapic_version as package_version @@ -478,4 +478,19 @@ replacements: # TODO(https://github.com/googleapis/google-cloud-python/issues/13362): # Enable mypy once this repo has been updated for mypy evaluation. session.skip("Skip mypy since this library is not yet updated for mypy evaluation") + + session.install("-e", ".") + session.install( + "mypy", + "types-setuptools", + "types-protobuf", + "types-requests", + ) + session.run( + "mypy", + f"--config-file={MYPY_CONFIG_FILE}", + "-p", + "google", + *session.posargs, + ) count: 1 diff --git a/.librarian/generator-input/client-post-processing/pubsub-integration.yaml b/.librarian/generator-input/client-post-processing/pubsub-integration.yaml index d90529897d1c..0c6e52d07afd 100644 --- a/.librarian/generator-input/client-post-processing/pubsub-integration.yaml +++ b/.librarian/generator-input/client-post-processing/pubsub-integration.yaml @@ -1,5 +1,5 @@ replacements: - - paths: + - paths: - "packages/google-cloud-pubsub/google/pubsub_v1/services/**/*client.py" before: 'instead\.\n(?:[ \t]*\n)*([ \t]+)DEFAULT_ENDPOINT = "pubsub\.googleapis\.com"' after: |- @@ -13,7 +13,7 @@ replacements: \g<1>DEFAULT_ENDPOINT = "pubsub.googleapis.com" count: 3 - - paths: + - paths: - "packages/google-cloud-pubsub/google/pubsub_v1/services/*/client.py" before: 'import json\n(?:[ \t]*\n)*import logging as std_logging' after: |- @@ -23,7 +23,7 @@ replacements: import logging as std_logging count: 3 - - paths: + - paths: - "packages/google-cloud-pubsub/google/pubsub_v1/services/*/client.py" before: 'class\n(?:[ \t]*\n)*([ \t]+)self\._transport = transport_init\(' after: |- @@ -38,7 +38,7 @@ replacements: \g<1>self._transport = transport_init( count: 3 - - paths: + - paths: - "packages/google-cloud-pubsub/google/pubsub_v1/services/*/transports/grpc*.py" - "packages/google-cloud-pubsub/tests/unit/gapic/pubsub_v1/*.py" before: '\("grpc\.max_receive_message_length", -1\),\n(?:[ \t]*\n)*([ \t]+)\]' @@ -49,19 +49,19 @@ replacements: \g<1>] count: 21 - - paths: + - paths: - "packages/google-cloud-pubsub/google/pubsub_v1/services/publisher/transports/base.py" before: 'deadline=60\.0,' after: 'deadline=600.0,' count: 9 - - paths: + - paths: - "packages/google-cloud-pubsub/google/pubsub_v1/services/**/*.py" before: 'gapic_version=package_version\.__version__' after: 'client_library_version=package_version.__version__' count: 9 - - paths: + - paths: - "packages/google-cloud-pubsub/google/pubsub_v1/services/subscriber/client.py" before: 'import logging as std_logging\n(?:[ \t]*\n)*import os' after: |- @@ -71,7 +71,7 @@ replacements: import os count: 1 - - paths: + - paths: - "packages/google-cloud-pubsub/google/pubsub_v1/services/subscriber/async_client.py" before: 'import logging as std_logging\n(?:[ \t]*\n)*import re' after: |- @@ -80,7 +80,7 @@ replacements: import re count: 1 - - paths: + - paths: - "packages/google-cloud-pubsub/google/pubsub_v1/services/subscriber/client.py" - "packages/google-cloud-pubsub/google/pubsub_v1/services/subscriber/async_client.py" before: 'request\.max_messages = max_messages\n(?:[ \t]*\n)*([ \t]+)# Wrap the RPC method' @@ -96,7 +96,7 @@ replacements: \g<1># Wrap the RPC method count: 2 - - paths: + - paths: - "packages/google-cloud-pubsub/google/pubsub_v1/services/subscriber/client.py" before: '"""\n(?:[ \t]*\n)*([ \t]+)# Wrap the RPC method; this adds retry and timeout information,\n(?:[ \t]*\n)*[ \t]+# and friendly error handling\.\n(?:[ \t]*\n)*[ \t]+rpc = self\._transport\._wrapped_methods\[self\._transport\.streaming_pull\]' after: |- @@ -403,6 +403,16 @@ replacements: before: 'nox\.options\.sessions = \[\n(?:[ \t]+)"unit",\n(?:[ \t]+)"system",\n(?:[ \t]+)"cover",\n(?:[ \t]+)"lint",\n(?:[ \t]+)"lint_setup_py",\n(?:[ \t]+)"blacken",\n(?:[ \t]+)"docs",\n\]' after: |- CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() + # Path to the centralized mypy configuration file at the repository root. + # Search upwards to support running nox from both monorepo packages and integration test goldens. + MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), + ) nox.options.sessions = [ "unit", @@ -450,7 +460,7 @@ replacements: # mypy checks yet. # https://github.com/googleapis/gapic-generator-python/issues/1092 # TODO: Re-enable mypy checks once we merge, since incremental checks are failing due to protobuf upgrade - # session.run("mypy", "-p", "google.cloud", "--exclude", "google/pubsub_v1/") + # session.run("mypy", f"--config-file={MYPY_CONFIG_FILE}", "-p", "google.cloud", "--exclude", "google/pubsub_v1/") @nox.session(python=DEFAULT_PYTHON_VERSION) diff --git a/.librarian/generator-input/client-post-processing/spanner-integration.yaml b/.librarian/generator-input/client-post-processing/spanner-integration.yaml index f4ae29dbf851..15f5d69b1366 100644 --- a/.librarian/generator-input/client-post-processing/spanner-integration.yaml +++ b/.librarian/generator-input/client-post-processing/spanner-integration.yaml @@ -649,6 +649,7 @@ replacements: "3.12", "3.13", "3.14", + "3.15", ] UNIT_TEST_STANDARD_DEPENDENCIES = [ "mock", @@ -827,30 +828,16 @@ replacements: @nox.session(python=ALL_PYTHON) @nox.parametrize( "protobuf_implementation", - ["python", "upb", "cpp"], + ["python", "upb"], ) def unit(session, protobuf_implementation): # Install all test dependencies, then install this package in-place. - if protobuf_implementation == "cpp" and session.python in ( - "3.11", - "3.12", - "3.13", - "3.14", - ): - session.skip("cpp implementation is not supported in python 3.11+") - constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" ) install_unittest_dependencies(session, "-c", constraints_path) - # TODO(https://github.com/googleapis/synthtool/issues/1976): - # Remove the 'cpp' implementation once support for Protobuf 3.x is dropped. - # The 'cpp' implementation requires Protobuf<4. - if protobuf_implementation == "cpp": - session.install("protobuf<4") - # Run py.test against the unit tests. args = [ "py.test", @@ -946,8 +933,6 @@ replacements: ("python", "POSTGRESQL"), ("upb", "GOOGLE_STANDARD_SQL"), ("upb", "POSTGRESQL"), - ("cpp", "GOOGLE_STANDARD_SQL"), - ("cpp", "POSTGRESQL"), ], ) def system(session, protobuf_implementation, database_dialect): @@ -975,14 +960,6 @@ replacements: "Only run system tests on real Spanner with one protobuf implementation to speed up the build" ) - if protobuf_implementation == "cpp" and session.python in ( - "3.11", - "3.12", - "3.13", - "3.14", - ): - session.skip("cpp implementation is not supported in python 3.11+") - # Install pyopenssl for mTLS testing. if os.environ.get("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true": session.install("pyopenssl") @@ -995,12 +972,6 @@ replacements: install_systemtest_dependencies(session, "-c", constraints_path) - # TODO(https://github.com/googleapis/synthtool/issues/1976): - # Remove the 'cpp' implementation once support for Protobuf 3.x is dropped. - # The 'cpp' implementation requires Protobuf<4. - if protobuf_implementation == "cpp": - session.install("protobuf<4") - # Run py.test against the system tests. if system_test_exists: args = [ @@ -1174,21 +1145,11 @@ replacements: ("python", "POSTGRESQL"), ("upb", "GOOGLE_STANDARD_SQL"), ("upb", "POSTGRESQL"), - ("cpp", "GOOGLE_STANDARD_SQL"), - ("cpp", "POSTGRESQL"), ], ) def prerelease_deps(session, protobuf_implementation, database_dialect): """Run all tests with prerelease versions of dependencies installed.""" - if protobuf_implementation == "cpp" and session.python in ( - "3.11", - "3.12", - "3.13", - "3.14", - ): - session.skip("cpp implementation is not supported in python 3.11+") - # Install all dependencies session.install("-e", ".[all, tests, tracing]") unit_deps_all = UNIT_TEST_STANDARD_DEPENDENCIES + UNIT_TEST_EXTERNAL_DEPENDENCIES diff --git a/.librarian/generator-input/client-post-processing/storage-integration.yaml b/.librarian/generator-input/client-post-processing/storage-integration.yaml index eb1fc8547227..d481ea6fa396 100644 --- a/.librarian/generator-input/client-post-processing/storage-integration.yaml +++ b/.librarian/generator-input/client-post-processing/storage-integration.yaml @@ -640,7 +640,7 @@ replacements: packages/google-cloud-storage/testing/constraints-3.10.txt ] before: | - google-api-core==2.24.2 + google-api-core==2.25.0 google-auth==2.14.1 grpcio==1.59.0 proto-plus==1.26.1 @@ -804,4 +804,19 @@ replacements: # TODO(https://github.com/googleapis/google-cloud-python/issues/13362): # Enable mypy once this repo has been updated for mypy evaluation. session.skip("Skip mypy since this library is not yet updated for mypy evaluation") + + session.install("-e", ".") + session.install( + "mypy", + "types-setuptools", + "types-protobuf", + "types-requests", + ) + session.run( + "mypy", + f"--config-file={MYPY_CONFIG_FILE}", + "-p", + "google", + *session.posargs, + ) count: 1 diff --git a/.release-please-bulk-manifest.json b/.release-please-bulk-manifest.json index 07641384c830..8a7a8474c297 100644 --- a/.release-please-bulk-manifest.json +++ b/.release-please-bulk-manifest.json @@ -2,7 +2,7 @@ "packages/bigquery-magics": "0.15.0", "packages/db-dtypes": "1.7.1", "packages/django-google-spanner": "5.0.0", - "packages/gapic-generator": "1.37.0", + "packages/gapic-generator": "1.37.1", "packages/gcp-sphinx-docfx-yaml": "3.3.0", "packages/google-ads-admanager": "0.10.0", "packages/google-ads-datamanager": "0.9.1", @@ -10,14 +10,14 @@ "packages/google-ai-generativelanguage": "0.12.0", "packages/google-analytics-admin": "0.30.1", "packages/google-analytics-data": "0.23.0", - "packages/google-api-core": "2.32.0", + "packages/google-api-core": "2.33.0", "packages/google-apps-card": "0.7.0", "packages/google-apps-chat": "0.10.3", "packages/google-apps-events-subscriptions": "0.6.0", "packages/google-apps-meet": "0.5.0", "packages/google-apps-script-type": "0.8.0", "packages/google-area120-tables": "0.15.0", - "packages/google-auth": "2.56.1", + "packages/google-auth": "2.56.2", "packages/google-auth-httplib2": "0.4.0", "packages/google-auth-oauthlib": "1.4.0", "packages/google-backstory": "0.1.0", @@ -79,7 +79,6 @@ "packages/google-cloud-cloudcontrolspartner": "0.6.0", "packages/google-cloud-cloudsecuritycompliance": "0.8.0", "packages/google-cloud-commerce-consumer-procurement": "0.6.0", - "packages/google-cloud-commerceproducer": "0.0.0", "packages/google-cloud-common": "1.10.0", "packages/google-cloud-compute": "1.50.0", "packages/google-cloud-compute-v1beta": "0.12.1", @@ -276,6 +275,7 @@ "packages/googleapis-common-protos": "1.75.0", "packages/grafeas": "1.23.0", "packages/grpc-google-iam-v1": "0.14.4", - "packages/proto-plus": "1.28.1", + "packages/proto-plus": "1.28.2", + "packages/sqlalchemy-bigquery": "1.17.1", "packages/sqlalchemy-spanner": "1.19.0" } \ No newline at end of file diff --git a/.release-please-individual-manifest.json b/.release-please-individual-manifest.json index 8060aa1a902c..36f9fa0440ee 100644 --- a/.release-please-individual-manifest.json +++ b/.release-please-individual-manifest.json @@ -1,6 +1,8 @@ { "packages/bigframes": "2.46.0", + "packages/google-cloud-commerceproducer": "0.1.0", + "packages/google-cloud-productregistry": "0.1.0", "packages/google-crc32c": "1.8.0", - "packages/pandas-gbq": "0.35.0", - "packages/sqlalchemy-bigquery": "1.17.0" + "packages/google-maps-isochrones": "0.1.0", + "packages/pandas-gbq": "0.35.0" } \ No newline at end of file diff --git a/librarian.yaml b/librarian.yaml index df572053ffa7..5699ac279a59 100644 --- a/librarian.yaml +++ b/librarian.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. language: python -version: v0.26.0 +version: v0.28.0 repo: googleapis/google-cloud-python sources: googleapis: @@ -74,7 +74,7 @@ libraries: python: library_type: INTEGRATION - name: gapic-generator - version: 1.37.0 + version: 1.37.1 python: library_type: CORE - name: gcp-sphinx-docfx-yaml @@ -134,7 +134,7 @@ libraries: metadata_name_override: analyticsdata default_version: v1beta - name: google-api-core - version: 2.32.0 + version: 2.33.0 python: library_type: CORE - name: google-apps-card @@ -227,7 +227,7 @@ libraries: metadata_name_override: area120tables default_version: v1alpha1 - name: google-auth - version: 2.56.1 + version: 2.56.2 python: library_type: AUTH - name: google-auth-httplib2 @@ -757,7 +757,7 @@ libraries: metadata_name_override: procurement default_version: v1 - name: google-cloud-commerceproducer - version: 0.0.0 + version: 0.1.0 apis: - path: google/cloud/commerceproducer/v1beta copyright_year: "2026" @@ -2310,6 +2310,13 @@ libraries: google/maps/geocode/v4: - proto-plus-deps=google.geo.type default_version: v4 + - name: google-maps-isochrones + version: 0.1.0 + apis: + - path: google/maps/isochrones/v1 + copyright_year: "2026" + python: + default_version: v1 - name: google-maps-mapmanagement version: 0.1.0 apis: @@ -2582,11 +2589,11 @@ libraries: python: library_type: INTEGRATION - name: proto-plus - version: 1.28.1 + version: 1.28.2 python: library_type: CORE - name: sqlalchemy-bigquery - version: 1.17.0 + version: 1.17.1 python: library_type: INTEGRATION - name: sqlalchemy-spanner diff --git a/mypy.ini b/mypy.ini index 05d59c9ea7b0..bf2a196c5ab7 100644 --- a/mypy.ini +++ b/mypy.ini @@ -27,31 +27,31 @@ ignore_missing_imports = True [mypy-flask] ignore_missing_imports = True -[mypy-google.api.*] +[mypy-google.api,google.api.*] ignore_missing_imports = True [mypy-google.colab] ignore_missing_imports = True -[mypy-google.iam.*] +[mypy-google.iam,google.iam.*] ignore_missing_imports = True -[mypy-google.longrunning.*] +[mypy-google.longrunning,google.longrunning.*] ignore_missing_imports = True -[mypy-google.oauth2.*] +[mypy-google.oauth2,google.oauth2.*] ignore_missing_imports = True -[mypy-google.protobuf.*] +[mypy-google.protobuf,google.protobuf.*] ignore_missing_imports = True -[mypy-google.rpc.*] +[mypy-google.rpc,google.rpc.*] ignore_missing_imports = True -[mypy-google.type.*] +[mypy-google.type,google.type.*] ignore_missing_imports = True -[mypy-grpc.*] +[mypy-grpc,grpc.*] ignore_missing_imports = True [mypy-grpc_status] @@ -63,7 +63,7 @@ ignore_missing_imports = True [mypy-ipywidgets] ignore_missing_imports = True -[mypy-proto.*] +[mypy-proto,proto.*] ignore_missing_imports = True [mypy-pyarrow.*] @@ -95,9 +95,11 @@ ignore_missing_imports = True ignore_missing_imports = True [mypy-google.cloud.bigtable.*] +# Broadly ignore errors for the older, legacy/handwritten parts of the library ignore_errors = True [mypy-google.cloud.bigtable.data.*] +# Stricter rules for the newer parts of the library check_untyped_defs = True warn_unreachable = True disallow_any_generics = True diff --git a/packages/bigframes/bigframes/display/table_widget_angular/package-lock.json b/packages/bigframes/bigframes/display/table_widget_angular/package-lock.json index 8312722d679f..61181940b646 100644 --- a/packages/bigframes/bigframes/display/table_widget_angular/package-lock.json +++ b/packages/bigframes/bigframes/display/table_widget_angular/package-lock.json @@ -5807,9 +5807,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.1.tgz", - "integrity": "sha512-h2r7rcm6Ee/J8o0LD5djLuFVcfbZxhvho4vvsbeV0aMvXjUgqv4YpxpkEx0d68l6+IleVfLAdVEfhR7QNMkGHQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", "dev": true, "funding": [ { @@ -6065,9 +6065,9 @@ } }, "node_modules/hono": { - "version": "4.12.16", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.16.tgz", - "integrity": "sha512-jN0ZewiNAWSe5khM3EyCmBb250+b40wWbwNILNfEvq84VREWwOIkuUsFONk/3i3nqkz7Oe1PcpM2mwQEK2L9Kg==", + "version": "4.12.31", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.31.tgz", + "integrity": "sha512-zJIHFrl6bq3RDd2YusFNCDlM8qUprxKswyi/OPzPyzKDdyBXDqWx8bZlZ7R+saTdSTatUmb3O7K4SspGPaEOQg==", "dev": true, "license": "MIT", "engines": { @@ -6230,9 +6230,9 @@ } }, "node_modules/immutable": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.7.tgz", - "integrity": "sha512-47Xb+LFbZ/ZIjQMj6Q5J3IfK7PJFuqRdFOC9FpGgRTK6U2dAEVmkR9hp58qU4FpYux5YXpneDwkj2EP6lppzFA==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", + "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", "dev": true, "license": "MIT" }, diff --git a/packages/bigframes/noxfile.py b/packages/bigframes/noxfile.py index 0a33264aa8ea..8cbbddfc61aa 100644 --- a/packages/bigframes/noxfile.py +++ b/packages/bigframes/noxfile.py @@ -334,9 +334,6 @@ def run_system( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" ) - if "GOOGLE_APPLICATION_CREDENTIALS" not in os.environ: - session.skip("Credentials must be set via environment variable") - # Check the value of `RUN_SYSTEM_TESTS` env var. It defaults to true. if os.environ.get("RUN_SYSTEM_TESTS", "true") == "false": session.skip("RUN_SYSTEM_TESTS is set to false, skipping") @@ -595,9 +592,6 @@ def docfx(session): def prerelease(session: nox.sessions.Session, tests_path, extra_pytest_options=()): - if "GOOGLE_APPLICATION_CREDENTIALS" not in os.environ: - session.skip("Credentials must be set via environment variable") - constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" ) diff --git a/packages/bigquery-magics/mypy.ini b/packages/bigquery-magics/mypy.ini deleted file mode 100644 index a3cb5c292172..000000000000 --- a/packages/bigquery-magics/mypy.ini +++ /dev/null @@ -1,3 +0,0 @@ -[mypy] -python_version = 3.14 -namespace_packages = True diff --git a/packages/bigquery-magics/noxfile.py b/packages/bigquery-magics/noxfile.py index f275406b6905..20f1d3b717e5 100644 --- a/packages/bigquery-magics/noxfile.py +++ b/packages/bigquery-magics/noxfile.py @@ -112,6 +112,17 @@ } CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) + nox.options.sessions = [ "unit", @@ -546,3 +557,18 @@ def mypy(session): # TODO(https://github.com/googleapis/google-cloud-python/issues/16014): # Add mypy tests session.skip("mypy tests are not yet supported") + + session.install("-e", ".") + session.install( + "mypy", + "types-setuptools", + "types-protobuf", + "types-requests", + ) + session.run( + "mypy", + f"--config-file={MYPY_CONFIG_FILE}", + "-p", + "bigquery_magics", + *session.posargs, + ) diff --git a/packages/db-dtypes/noxfile.py b/packages/db-dtypes/noxfile.py index b80b1df6832a..afd1ab00a637 100644 --- a/packages/db-dtypes/noxfile.py +++ b/packages/db-dtypes/noxfile.py @@ -70,6 +70,17 @@ SYSTEM_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = {} CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) + # 'docfx' is excluded since it only needs to run in 'docs-presubmit' nox.options.sessions = [ @@ -541,6 +552,7 @@ def mypy(session): session.install("-e", ".") session.run( "mypy", + f"--config-file={MYPY_CONFIG_FILE}", "-p", "db_dtypes", "--check-untyped-defs", diff --git a/packages/django-google-spanner/.coveragerc b/packages/django-google-spanner/.coveragerc index a1d7fd2debcd..fd5adc4f59da 100644 --- a/packages/django-google-spanner/.coveragerc +++ b/packages/django-google-spanner/.coveragerc @@ -8,9 +8,16 @@ [run] branch = True +source = + django_spanner + +[paths] +source = + django_spanner + */site-packages/django_spanner [report] -fail_under = 80 +fail_under = 68 show_missing = True exclude_lines = # Re-enable the standard pragma @@ -20,4 +27,5 @@ exclude_lines = # Ignore abstract methods raise NotImplementedError omit = - */site-packages/*.py \ No newline at end of file + tests/* + */tests/* \ No newline at end of file diff --git a/packages/django-google-spanner/noxfile.py b/packages/django-google-spanner/noxfile.py index 0b94e5b87bff..b867418df7bf 100644 --- a/packages/django-google-spanner/noxfile.py +++ b/packages/django-google-spanner/noxfile.py @@ -66,6 +66,16 @@ ] CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) @nox.session(python=DEFAULT_PYTHON_VERSION) @@ -118,21 +128,21 @@ def default(session): session.install( *UNIT_TEST_STANDARD_DEPENDENCIES, *UNIT_TEST_EXTERNAL_DEPENDENCIES, - *UNIT_TEST_DEPENDENCIES, + *UNIT_TEST_MOCKSERVER_DEPENDENCIES, ) session.install("-e", ".") - # Run py.test against the unit tests. + # Run py.test against unit and mockserver tests. session.run( "py.test", "--quiet", "--cov=django_spanner", - "--cov=tests.unit", "--cov-append", "--cov-config=.coveragerc", "--cov-report=", - "--cov-fail-under=75", + "--cov-fail-under=0", os.path.join("tests", "unit"), + os.path.join("tests", "mockserver_tests"), *session.posargs, ) @@ -155,6 +165,12 @@ def mockserver(session): session.run( "py.test", "--quiet", + "--cov=django_spanner", + "--cov=tests.mockserver_tests", + "--cov-append", + "--cov-config=.coveragerc", + "--cov-report=", + "--cov-fail-under=0", os.path.join("tests", "mockserver_tests"), *session.posargs, ) @@ -321,6 +337,21 @@ def mypy(session): # Add typehints to this package. session.skip("Typehints and thus mypy are not yet supported.") + session.install("-e", ".") + session.install( + "mypy", + "types-setuptools", + "types-protobuf", + "types-requests", + ) + session.run( + "mypy", + f"--config-file={MYPY_CONFIG_FILE}", + "-p", + "django_spanner", + *session.posargs, + ) + @nox.session(python=DEFAULT_PYTHON_VERSION) @nox.parametrize( diff --git a/packages/gapic-generator/CHANGELOG.md b/packages/gapic-generator/CHANGELOG.md index 4f5e5595474a..9ce38dc6a0e7 100644 --- a/packages/gapic-generator/CHANGELOG.md +++ b/packages/gapic-generator/CHANGELOG.md @@ -4,6 +4,14 @@ [1]: https://pypi.org/project/gapic-generator/#history +## [1.37.1](https://github.com/googleapis/google-cloud-python/compare/gapic-generator-v1.37.0...gapic-generator-v1.37.1) (2026-07-22) + + +### Bug Fixes + +* **gapic-generator:** remove lagging 3.9 refs ([#16924](https://github.com/googleapis/google-cloud-python/issues/16924)) ([a719055](https://github.com/googleapis/google-cloud-python/commit/a7190556f487028f04e42ade1f922075127028c3)) +* **gapic:** mock os.path.exists in mTLS tests to support newer google auth ([#17807](https://github.com/googleapis/google-cloud-python/issues/17807)) ([df0541a](https://github.com/googleapis/google-cloud-python/commit/df0541abf6aae710fc3e6644a5d8f3f706d29c5a)) + ## [1.37.0](https://github.com/googleapis/google-cloud-python/compare/gapic-generator-v1.36.0...gapic-generator-v1.37.0) (2026-07-07) diff --git a/packages/gapic-generator/gapic/generator/generator.py b/packages/gapic-generator/gapic/generator/generator.py index 9fe56aa9de1d..c1932abbe426 100644 --- a/packages/gapic-generator/gapic/generator/generator.py +++ b/packages/gapic-generator/gapic/generator/generator.py @@ -120,7 +120,7 @@ def get_response(self, api_schema: api.API, opts: Options) -> CodeGeneratorRespo for template_name in client_templates: # Quick check: Skip "private" templates. filename = template_name.split("/")[-1] - if filename.startswith("_") and filename != "__init__.py.j2": + if filename.startswith("_") and filename not in ("__init__.py.j2", "_compat.py.j2"): continue # Append to the output files dictionary. diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 new file mode 100644 index 000000000000..894ed4db0fb4 --- /dev/null +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 @@ -0,0 +1,303 @@ +# {% include '_license.j2' %} + +"""A compatibility module for older versions of google-api-core.""" + +import functools +import json +import operator +import os +import re +import uuid +from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from google.auth.exceptions import MutualTLSChannelError +import google.protobuf.message + + +try: + from google.api_core.universe import ( + get_default_mtls_endpoint, + get_api_endpoint, + get_universe_domain, + ) +except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]: + """Converts api endpoint to mTLS 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) + if m is None: + # Could not parse api_endpoint; return as-is. + return 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") + + def get_api_endpoint( + api_override: Optional[str], + client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + universe_domain: str, + use_mtls_endpoint: str, + default_universe: str, + default_mtls_endpoint: Optional[str], + default_endpoint_template: str, + ) -> Optional[str]: + """Return the API endpoint 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): + if universe_domain != default_universe: + raise MutualTLSChannelError( + f"mTLS is not supported in any universe other than {default_universe}." + ) + api_endpoint = default_mtls_endpoint + else: + api_endpoint = default_endpoint_template.format(UNIVERSE_DOMAIN=universe_domain) + return api_endpoint + + def get_universe_domain( + client_universe_domain: Optional[str], + universe_domain_env: Optional[str], + default_universe: str, + ) -> str: + """Return the universe domain used by the client.""" + universe_domain = 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 + + +try: + from google.api_core.gapic_v1.config import ( + use_client_cert_effective, + get_client_cert_source, + read_environment_variables, + ) +except ImportError: + from google.auth.transport import mtls # type: ignore + + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + + def use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + if hasattr(mtls, "should_use_client_cert"): + return mtls.should_use_client_cert() + else: + use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() + if use_client_cert_str not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" + " either `true` or `false`" + ) + return use_client_cert_str == "true" + + def get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return 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 ( + hasattr(mtls, "has_default_client_cert_source") + and mtls.has_default_client_cert_source() + ): + client_cert_source = mtls.default_client_cert_source() + else: + raise ValueError( + "Client certificate is required for mTLS, but no client certificate source was provided or found." + ) + return client_cert_source + + def read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + use_client_cert = use_client_cert_effective() + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() + universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") + 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, use_mtls_endpoint, universe_domain_env + + +try: + from google.api_core.gapic_v1.request import setup_request_id # type: ignore +except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. + def setup_request_id(request, field_name: str, is_proto3_optional: bool): + """Populate a UUID4 field in the request if it is not already set. + + Args: + request (Union[google.protobuf.message.Message, dict]): The request object. + field_name (str): The name of the field to populate. + is_proto3_optional (bool): Whether the field is proto3 optional. + """ + request_id_val = str(uuid.uuid4()) + if request is None: + return + + if isinstance(request, dict): + if is_proto3_optional: + if field_name not in request or request[field_name] is None: + request[field_name] = request_id_val + elif not request.get(field_name): + request[field_name] = request_id_val + return + + if is_proto3_optional: + try: + # Pure protobuf messages + if not request.HasField(field_name): + setattr(request, field_name, request_id_val) + except (AttributeError, ValueError): + # Proto-plus messages or other objects + if getattr(request, field_name, None) is None: + setattr(request, field_name, request_id_val) + else: + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) + + +try: + from google.api_core.rest_helpers import ( # type: ignore + flatten_query_params, + transcode_request as _core_transcode_request, + ) + import inspect + if "rest_numeric_enums" not in inspect.signature(_core_transcode_request).parameters: + raise ImportError + transcode_request = _core_transcode_request +except (ImportError, AttributeError): # pragma: NO COVER + # TODO: Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + from google.protobuf import json_format # type: ignore + from google.api_core import path_template # type: ignore + + def flatten_query_params(obj, strict=False): # pragma: NO COVER + if obj is not None and not isinstance(obj, dict): + raise TypeError("flatten_query_params must be called with dict object") + return _flatten(obj, key_path=[], strict=strict) + + def _flatten(obj, key_path, strict=False): # pragma: NO COVER + if obj is None: + return [] + if isinstance(obj, dict): + return _flatten_dict(obj, key_path=key_path, strict=strict) + if isinstance(obj, list): + return _flatten_list(obj, key_path=key_path, strict=strict) + return _flatten_value(obj, key_path=key_path, strict=strict) + + def _is_primitive_value(obj): # pragma: NO COVER + if obj is None: + return False + if isinstance(obj, (list, dict)): + raise ValueError("query params may not contain repeated dicts or lists") + return True + + def _flatten_value(obj, key_path, strict=False): # pragma: NO COVER + return [(".".join(key_path), _canonicalize(obj, strict=strict))] + + def _flatten_dict(obj, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten(value, key_path=key_path + [key], strict=strict) + for key, value in obj.items() + ) + return functools.reduce(operator.concat, items, []) # type: ignore + + def _flatten_list(elems, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten_value(elem, key_path=key_path, strict=strict) + for elem in elems + if _is_primitive_value(elem) + ) + return functools.reduce(operator.concat, items, []) # type: ignore + + def _canonicalize(obj, strict=False): # pragma: NO COVER + if strict: + value = str(obj) + if isinstance(obj, bool): + value = value.lower() + return value + return obj + + def transcode_request( # pragma: NO COVER + http_options: List[Dict[str, str]], + request: Any, + required_fields_default_values: Optional[Dict[str, Any]] = None, + rest_numeric_enums: bool = False, + ) -> Tuple[Dict[str, Any], Optional[str], Dict[str, Any]]: + pb_request = getattr(request, "_pb", request) + transcoded_request = path_template.transcode(http_options, pb_request) + + body_json = None + if transcoded_request.get("body") is not None: + body_json = json_format.MessageToJson( + transcoded_request["body"], + use_integers_for_enums=rest_numeric_enums, + ) + + query_params_json = {} + if transcoded_request.get("query_params") is not None: + query_params_json = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + use_integers_for_enums=rest_numeric_enums, + ) + ) + + if required_fields_default_values: + matched_option = None + for option in http_options: + if option.get("method", "").lower() == transcoded_request.get("method", "").lower(): + if path_template.validate( + option.get("uri", ""), transcoded_request.get("uri", "") + ): + matched_option = option + break + + bound_fields = set() + if matched_option: + uri_template = matched_option.get("uri", "") + for m in path_template._VARIABLE_RE.finditer(uri_template): + name = m.group("name") + if name: + bound_fields.add(name.split(".")[0]) + body_param = matched_option.get("body") + if body_param: + if body_param == "*": + bound_fields = None + else: + bound_fields.add(body_param.split(".")[0]) + + if bound_fields is not None: + for k, v in required_fields_default_values.items(): + if k in bound_fields: + continue + if k not in query_params_json: + query_params_json[k] = v + + if rest_numeric_enums: + query_params_json["$alt"] = "json;enum-encoding=int" + + return transcoded_request, body_json, query_params_json + diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/_shared_macros.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/_shared_macros.j2 index 755e4530e7ba..584b77e580d7 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/_shared_macros.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/_shared_macros.j2 @@ -198,22 +198,23 @@ def _get_http_options(): service: The service. is_async (bool): Used to determine the code path i.e. whether for sync or async call. is_request_message_proto_plus_type (bool): Used to determine whether the request message is a proto-plus type. #} -{% macro rest_call_method_common(body_spec, method_name, service, is_async=False, is_request_message_proto_plus_type=False) %} +{% macro rest_call_method_common(body_spec, method_name, service, is_async=False, is_request_message_proto_plus_type=False, rest_numeric_enums=False) %} {% set service_name = service.name %} {% set await_prefix = "await " if is_async else "" %} {% set async_class_prefix = "Async" if is_async else "" %} http_options = _Base{{ service_name }}RestTransport._Base{{method_name}}._get_http_options() - {# TODO(https://github.com/googleapis/gapic-generator-python/issues/2274): Add debug log before intercepting a request #} request, metadata = {{ await_prefix }}self._interceptor.pre_{{ method_name|snake_case }}(request, metadata) - transcoded_request = _Base{{ service_name }}RestTransport._Base{{method_name}}._get_transcoded_request(http_options, request) - - {% if body_spec %} - body = _Base{{ service_name }}RestTransport._Base{{method_name}}._get_request_body_json(transcoded_request) - {% endif %}{# body_spec #} - - # Jsonify the query params - query_params = _Base{{ service_name }}RestTransport._Base{{method_name}}._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _Base{{ service_name }}RestTransport._Base{{method_name}}, + "_Base{{method_name}}__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums={{ rest_numeric_enums }}, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/_rest_mixins_base.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/_rest_mixins_base.py.j2 index 16cc77ea937c..042de855681e 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/_rest_mixins_base.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/_rest_mixins_base.py.j2 @@ -20,32 +20,11 @@ {% for name, sig in api.mixin_api_signatures.items() %} class _Base{{ name }}: + """Base class for {{ name }} REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") {{ shared_macros.http_options_method(api.mixin_http_options["{}".format(name)])|indent(8)}} - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - {% set body_spec = api.mixin_http_options["{}".format(name)][0].body %} - {%- if body_spec %} - - @staticmethod - def _get_request_body_json(transcoded_request): - body = json.dumps(transcoded_request['body']) - return body - - {%- endif %} {# body_spec #} - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - {% endfor %} {% endif %} {# rest in opts.transport #} diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest.py.j2 index 49c1374053b5..a3e08a447d06 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest.py.j2 @@ -10,7 +10,8 @@ from google.auth.transport.requests import AuthorizedSession # type: ignore from google.auth import credentials as ga_credentials # type: ignore from google.api_core import exceptions as core_exceptions from google.api_core import retry as retries -from google.api_core import rest_helpers +{% set package_path = api.naming.module_namespace|join('.') + "." + api.naming.versioned_module_name %} +from {{package_path}} import _compat as rest_helpers from google.api_core import rest_streaming from google.api_core import gapic_v1 import google.protobuf @@ -245,7 +246,7 @@ class {{service.name}}RestTransport(_Base{{ service.name }}RestTransport): {% endif %} """ - {{ shared_macros.rest_call_method_common(body_spec, method.name, service, False, method.input.ident.is_proto_plus_type)|indent(8) }} + {{ shared_macros.rest_call_method_common(body_spec, method.name, service, False, method.input.ident.is_proto_plus_type, opts.rest_numeric_enums)|indent(8) }} {% if not method.void %} # Return the response diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest_asyncio.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest_asyncio.py.j2 index 80980572c30a..83c8cd28b986 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest_asyncio.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest_asyncio.py.j2 @@ -32,7 +32,8 @@ from google.iam.v1 import policy_pb2 # type: ignore from google.cloud.location import locations_pb2 # type: ignore {% endif %} from google.api_core import retry_async as retries -from google.api_core import rest_helpers +{% set package_path = api.naming.module_namespace|join('.') + "." + api.naming.versioned_module_name %} +from {{package_path}} import _compat as rest_helpers from google.api_core import rest_streaming_async # type: ignore import google.protobuf @@ -203,7 +204,7 @@ class Async{{service.name}}RestTransport(_Base{{ service.name }}RestTransport): {% endif %} """ - {{ shared_macros.rest_call_method_common(body_spec, method.name, service, True, method.input.ident.is_proto_plus_type)|indent(8) }} + {{ shared_macros.rest_call_method_common(body_spec, method.name, service, True, method.input.ident.is_proto_plus_type, opts.rest_numeric_enums)|indent(8) }} {% if not method.void %} # Return the response diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest_base.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest_base.py.j2 index b79785afc517..90c0e10d6403 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest_base.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest_base.py.j2 @@ -120,51 +120,8 @@ class _Base{{ service.name }}RestTransport({{service.name}}Transport): 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} {% endif %}{# required fields #} - {% set method_http_options = method.http_options %} - {{ shared_macros.http_options_method(method_http_options)|indent(8) }} - - @staticmethod - def _get_transcoded_request(http_options, request): - {% if method.input.ident.is_proto_plus_type %} - pb_request = {{method.input.ident}}.pb(request) - {% else %} - pb_request = request - {% endif %} - transcoded_request = path_template.transcode(http_options, pb_request) - return transcoded_request - - {% set body_spec = method.http_options[0].body %} - {%- if body_spec %} - - @staticmethod - def _get_request_body_json(transcoded_request): - # Jsonify the request body - - body = json_format.MessageToJson( - transcoded_request['body'], - use_integers_for_enums={{ opts.rest_numeric_enums }} - ) - return body - - {%- endif %}{# body_spec #} - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json_format.MessageToJson( - transcoded_request['query_params'], - use_integers_for_enums={{ opts.rest_numeric_enums }}, - )) - {% if method.input.required_fields %} - query_params.update(_Base{{ service.name }}RestTransport._Base{{method.name}}._get_unset_required_fields(query_params)) - {% endif %}{# required fields #} - - {% if opts.rest_numeric_enums %} - query_params["$alt"] = "json;enum-encoding=int" - {% endif %} - return query_params - {% endif %}{# method.http_options and not method.client_streaming #} {% endfor %} diff --git a/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 b/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 index c68b390b7c84..c432b75621ea 100644 --- a/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 +++ b/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 @@ -174,13 +174,13 @@ def test__get_default_mtls_endpoint(): def test__read_environment_variables(): assert {{ service.client_name }}._read_environment_variables() == (False, "auto", None) - + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): assert {{ service.client_name }}._read_environment_variables() == (True, "auto", None) - + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): assert {{ service.client_name }}._read_environment_variables() == (False, "auto", None) - + with mock.patch.dict( os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} ): @@ -200,10 +200,10 @@ def test__read_environment_variables(): with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): assert {{ service.client_name }}._read_environment_variables() == (False, "never", None) - + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): assert {{ service.client_name }}._read_environment_variables() == (False, "always", None) - + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}): assert {{ service.client_name }}._read_environment_variables() == (False, "auto", None) @@ -293,7 +293,7 @@ def test_use_client_cert_effective(): assert {{ service.client_name }}._use_client_cert_effective() is False # Test case 12: Test when `should_use_client_cert` is available and the - # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also, + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also, # the GOOGLE_API_CONFIG environment variable is unset. if hasattr(google.auth.transport.mtls, "should_use_client_cert"): with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": ""}): @@ -303,7 +303,7 @@ def test_use_client_cert_effective(): def test__get_client_cert_source(): mock_provided_cert_source = mock.Mock() mock_default_cert_source = mock.Mock() - + assert {{ service.client_name }}._get_client_cert_source(None, False) is None assert {{ service.client_name }}._get_client_cert_source(mock_provided_cert_source, False) is None assert {{ service.client_name }}._get_client_cert_source(mock_provided_cert_source, True) == mock_provided_cert_source @@ -867,7 +867,7 @@ def test_{{ service.client_name|snake_case }}_get_mtls_endpoint_and_cert_source( ) assert api_endpoint == mock_api_endpoint assert cert_source is None - + # Test cases for mTLS enablement when GOOGLE_API_USE_CLIENT_CERTIFICATE is unset. test_cases = [ ( @@ -900,7 +900,7 @@ def test_{{ service.client_name|snake_case }}_get_mtls_endpoint_and_cert_source( config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -929,10 +929,10 @@ def test_{{ service.client_name|snake_case }}_get_mtls_endpoint_and_cert_source( }, }, mock_client_cert_source, - ), + ), ( # With workloads not present in config, mTLS is disabled. - { + { "version": 1, "cert_configs": {}, }, @@ -947,7 +947,7 @@ def test_{{ service.client_name|snake_case }}_get_mtls_endpoint_and_cert_source( config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -994,7 +994,7 @@ def test_{{ service.client_name|snake_case }}_get_mtls_endpoint_and_cert_source( 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`" @pytest.mark.parametrize("client_class", [ @@ -1059,7 +1059,7 @@ def test_{{ service.client_name|snake_case }}_client_api_endpoint(client_class): 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", [ {% if 'grpc' in opts.transport %} diff --git a/packages/gapic-generator/mypy.ini b/packages/gapic-generator/mypy.ini deleted file mode 100644 index ecb455a69d99..000000000000 --- a/packages/gapic-generator/mypy.ini +++ /dev/null @@ -1,2 +0,0 @@ -[mypy] -python_version = 3.14 diff --git a/packages/gapic-generator/noxfile.py b/packages/gapic-generator/noxfile.py index 52209f41ff38..621a37b9278c 100644 --- a/packages/gapic-generator/noxfile.py +++ b/packages/gapic-generator/noxfile.py @@ -36,6 +36,18 @@ showcase_version = os.environ.get("SHOWCASE_VERSION", "0.35.0") ADS_TEMPLATES = path.join(path.dirname(__file__), "gapic", "ads-templates") +CURRENT_DIRECTORY = Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) + RUFF_VERSION = "ruff==0.14.14" LINT_PATHS = ["docs", "gapic", "tests", "test_utils", "noxfile.py", "setup.py"] # Ruff uses globs for excludes (different from Black's regex) @@ -486,9 +498,7 @@ def showcase_pqc( """Run the Showcase PQC verification test suite against grpcio 1.83+ over standard TLS.""" with showcase_library(session, templates=templates, other_opts=other_opts): session.install("pytest", "pytest-asyncio") - # TODO(https://github.com/googleapis/google-cloud-python/issues/17751): - # Update the version below to `1.83.0` once released, and remove `--pre`. - session.install("--pre", "--upgrade", "grpcio>=1.83.0rc0", "grpcio-status>=1.83.0rc0") + session.install("--upgrade", "grpcio>=1.83.0", "grpcio-status>=1.83.0") session.run("py.test", "--quiet", "--tls", *(session.posargs or ["tests/system/test_pqc.py"]), env=env) @@ -730,7 +740,7 @@ def mypy(session): "click==8.1.3", ) session.install(".") - session.run("mypy", "-p", "gapic") + session.run("mypy", f"--config-file={MYPY_CONFIG_FILE}", "-p", "gapic") @nox.session(python=NEWEST_PYTHON) @@ -742,7 +752,7 @@ def lint(session): """ # TODO(https://github.com/googleapis/google-cloud-python/issues/16186): - # SKIP: This session was not enforced in the standalone (split) repo + # SKIP: This session was not enforced in the standalone (split) repo # and is disabled here to ensure a "move-only" migration. session.skip( "Linting was not enforced in the split repo. " @@ -772,9 +782,11 @@ def lint(session): @nox.session(python=NEWEST_PYTHON) def lint_setup_py(session): # TODO(https://github.com/googleapis/google-cloud-python/issues/16186): - # SKIP: This session was not enforced in the standalone (split) repo + # SKIP: This session was not enforced in the standalone (split) repo # and is disabled here to ensure a "move-only" migration. - session.skip("Skipping now to avoid changing code during migration. See Issue #16186") + session.skip( + "Skipping now to avoid changing code during migration. See Issue #16186" + ) @nox.session(python="3.10") @@ -849,9 +861,11 @@ def prerelease_deps(session, protobuf_implementation): """ Run all tests with pre-release versions of dependencies installed. """ - # TODO(https://github.com/googleapis/google-cloud-python/issues/16184): + # TODO(https://github.com/googleapis/google-cloud-python/issues/16184): # Implement pre-release dependency logic to test against upcoming runtime changes. - session.skip("prerelease_deps session is not yet implemented for gapic-generator-python.") + session.skip( + "prerelease_deps session is not yet implemented for gapic-generator-python." + ) @nox.session(python=NEWEST_PYTHON) @@ -861,6 +875,8 @@ def prerelease_deps(session, protobuf_implementation): ) def core_deps_from_source(session, protobuf_implementation): """Run all tests with core dependencies installed from source.""" - # TODO(https://github.com/googleapis/google-cloud-python/issues/16185): + # TODO(https://github.com/googleapis/google-cloud-python/issues/16185): # Implement logic to install core packages directly from the mono-repo directories. - session.skip("core_deps_from_source session is not yet implemented for gapic-generator-python.") \ No newline at end of file + session.skip( + "core_deps_from_source session is not yet implemented for gapic-generator-python." + ) diff --git a/packages/gapic-generator/setup.py b/packages/gapic-generator/setup.py index da2cccde365a..0eaefae80754 100644 --- a/packages/gapic-generator/setup.py +++ b/packages/gapic-generator/setup.py @@ -22,7 +22,7 @@ name = "gapic-generator" description = "Google API Client Generator for Python" url = "https://github.com/googleapis/google-cloud-python/tree/main/packages/gapic-generator" -version = "1.37.0" +version = "1.37.1" release_status = "Development Status :: 5 - Production/Stable" dependencies = [ # Ensure that the lower bounds of these dependencies match what we have in the diff --git a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py new file mode 100644 index 000000000000..6f17d9fcc3ea --- /dev/null +++ b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py @@ -0,0 +1,314 @@ +# # Copyright 2026 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. +# +"""A compatibility module for older versions of google-api-core.""" + +import functools +import json +import operator +import os +import re +import uuid +from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from google.auth.exceptions import MutualTLSChannelError +import google.protobuf.message + + +try: + from google.api_core.universe import ( + get_default_mtls_endpoint, + get_api_endpoint, + get_universe_domain, + ) +except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]: + """Converts api endpoint to mTLS 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) + if m is None: + # Could not parse api_endpoint; return as-is. + return 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") + + def get_api_endpoint( + api_override: Optional[str], + client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + universe_domain: str, + use_mtls_endpoint: str, + default_universe: str, + default_mtls_endpoint: Optional[str], + default_endpoint_template: str, + ) -> Optional[str]: + """Return the API endpoint 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): + if universe_domain != default_universe: + raise MutualTLSChannelError( + f"mTLS is not supported in any universe other than {default_universe}." + ) + api_endpoint = default_mtls_endpoint + else: + api_endpoint = default_endpoint_template.format(UNIVERSE_DOMAIN=universe_domain) + return api_endpoint + + def get_universe_domain( + client_universe_domain: Optional[str], + universe_domain_env: Optional[str], + default_universe: str, + ) -> str: + """Return the universe domain used by the client.""" + universe_domain = 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 + + +try: + from google.api_core.gapic_v1.config import ( + use_client_cert_effective, + get_client_cert_source, + read_environment_variables, + ) +except ImportError: + from google.auth.transport import mtls # type: ignore + + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + + def use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + if hasattr(mtls, "should_use_client_cert"): + return mtls.should_use_client_cert() + else: + use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() + if use_client_cert_str not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" + " either `true` or `false`" + ) + return use_client_cert_str == "true" + + def get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return 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 ( + hasattr(mtls, "has_default_client_cert_source") + and mtls.has_default_client_cert_source() + ): + client_cert_source = mtls.default_client_cert_source() + else: + raise ValueError( + "Client certificate is required for mTLS, but no client certificate source was provided or found." + ) + return client_cert_source + + def read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + use_client_cert = use_client_cert_effective() + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() + universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") + 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, use_mtls_endpoint, universe_domain_env + + +try: + from google.api_core.gapic_v1.request import setup_request_id # type: ignore +except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. + def setup_request_id(request, field_name: str, is_proto3_optional: bool): + """Populate a UUID4 field in the request if it is not already set. + + Args: + request (Union[google.protobuf.message.Message, dict]): The request object. + field_name (str): The name of the field to populate. + is_proto3_optional (bool): Whether the field is proto3 optional. + """ + request_id_val = str(uuid.uuid4()) + if request is None: + return + + if isinstance(request, dict): + if is_proto3_optional: + if field_name not in request or request[field_name] is None: + request[field_name] = request_id_val + elif not request.get(field_name): + request[field_name] = request_id_val + return + + if is_proto3_optional: + try: + # Pure protobuf messages + if not request.HasField(field_name): + setattr(request, field_name, request_id_val) + except (AttributeError, ValueError): + # Proto-plus messages or other objects + if getattr(request, field_name, None) is None: + setattr(request, field_name, request_id_val) + else: + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) + + +try: + from google.api_core.rest_helpers import ( # type: ignore + flatten_query_params, + transcode_request as _core_transcode_request, + ) + import inspect + if "rest_numeric_enums" not in inspect.signature(_core_transcode_request).parameters: + raise ImportError + transcode_request = _core_transcode_request +except (ImportError, AttributeError): # pragma: NO COVER + # TODO: Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + from google.protobuf import json_format # type: ignore + from google.api_core import path_template # type: ignore + + def flatten_query_params(obj, strict=False): # pragma: NO COVER + if obj is not None and not isinstance(obj, dict): + raise TypeError("flatten_query_params must be called with dict object") + return _flatten(obj, key_path=[], strict=strict) + + def _flatten(obj, key_path, strict=False): # pragma: NO COVER + if obj is None: + return [] + if isinstance(obj, dict): + return _flatten_dict(obj, key_path=key_path, strict=strict) + if isinstance(obj, list): + return _flatten_list(obj, key_path=key_path, strict=strict) + return _flatten_value(obj, key_path=key_path, strict=strict) + + def _is_primitive_value(obj): # pragma: NO COVER + if obj is None: + return False + if isinstance(obj, (list, dict)): + raise ValueError("query params may not contain repeated dicts or lists") + return True + + def _flatten_value(obj, key_path, strict=False): # pragma: NO COVER + return [(".".join(key_path), _canonicalize(obj, strict=strict))] + + def _flatten_dict(obj, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten(value, key_path=key_path + [key], strict=strict) + for key, value in obj.items() + ) + return functools.reduce(operator.concat, items, []) # type: ignore + + def _flatten_list(elems, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten_value(elem, key_path=key_path, strict=strict) + for elem in elems + if _is_primitive_value(elem) + ) + return functools.reduce(operator.concat, items, []) # type: ignore + + def _canonicalize(obj, strict=False): # pragma: NO COVER + if strict: + value = str(obj) + if isinstance(obj, bool): + value = value.lower() + return value + return obj + + def transcode_request( # pragma: NO COVER + http_options: List[Dict[str, str]], + request: Any, + required_fields_default_values: Optional[Dict[str, Any]] = None, + rest_numeric_enums: bool = False, + ) -> Tuple[Dict[str, Any], Optional[str], Dict[str, Any]]: + pb_request = getattr(request, "_pb", request) + transcoded_request = path_template.transcode(http_options, pb_request) + + body_json = None + if transcoded_request.get("body") is not None: + body_json = json_format.MessageToJson( + transcoded_request["body"], + use_integers_for_enums=rest_numeric_enums, + ) + + query_params_json = {} + if transcoded_request.get("query_params") is not None: + query_params_json = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + use_integers_for_enums=rest_numeric_enums, + ) + ) + + if required_fields_default_values: + matched_option = None + for option in http_options: + if option.get("method", "").lower() == transcoded_request.get("method", "").lower(): + if path_template.validate( + option.get("uri", ""), transcoded_request.get("uri", "") + ): + matched_option = option + break + + bound_fields = set() + if matched_option: + uri_template = matched_option.get("uri", "") + for m in path_template._VARIABLE_RE.finditer(uri_template): + name = m.group("name") + if name: + bound_fields.add(name.split(".")[0]) + body_param = matched_option.get("body") + if body_param: + if body_param == "*": + bound_fields = None + else: + bound_fields.add(body_param.split(".")[0]) + + if bound_fields is not None: + for k, v in required_fields_default_values.items(): + if k in bound_fields: + continue + if k not in query_params_json: + query_params_json[k] = v + + if rest_numeric_enums: + query_params_json["$alt"] = "json;enum-encoding=int" + + return transcoded_request, body_json, query_params_json diff --git a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/rest.py b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/rest.py index a9a4b4693298..83e0ef75d5b8 100755 --- a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/rest.py +++ b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/rest.py @@ -20,7 +20,7 @@ from google.auth import credentials as ga_credentials # type: ignore from google.api_core import exceptions as core_exceptions from google.api_core import retry as retries -from google.api_core import rest_helpers +from google.cloud.asset_v1 import _compat as rest_helpers from google.api_core import rest_streaming from google.api_core import gapic_v1 import google.protobuf @@ -1235,12 +1235,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseAnalyzeIamPolicy._get_http_options() - request, metadata = self._interceptor.pre_analyze_iam_policy(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseAnalyzeIamPolicy._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseAnalyzeIamPolicy._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseAnalyzeIamPolicy, + "_BaseAnalyzeIamPolicy__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1360,14 +1365,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseAnalyzeIamPolicyLongrunning._get_http_options() - request, metadata = self._interceptor.pre_analyze_iam_policy_longrunning(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseAnalyzeIamPolicyLongrunning._get_transcoded_request(http_options, request) - - body = _BaseAssetServiceRestTransport._BaseAnalyzeIamPolicyLongrunning._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseAnalyzeIamPolicyLongrunning._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseAnalyzeIamPolicyLongrunning, + "_BaseAnalyzeIamPolicyLongrunning__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1482,12 +1490,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseAnalyzeMove._get_http_options() - request, metadata = self._interceptor.pre_analyze_move(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseAnalyzeMove._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseAnalyzeMove._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseAnalyzeMove, + "_BaseAnalyzeMove__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1604,12 +1617,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseAnalyzeOrgPolicies._get_http_options() - request, metadata = self._interceptor.pre_analyze_org_policies(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseAnalyzeOrgPolicies._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseAnalyzeOrgPolicies._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseAnalyzeOrgPolicies, + "_BaseAnalyzeOrgPolicies__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1727,12 +1745,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseAnalyzeOrgPolicyGovernedAssets._get_http_options() - request, metadata = self._interceptor.pre_analyze_org_policy_governed_assets(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseAnalyzeOrgPolicyGovernedAssets._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseAnalyzeOrgPolicyGovernedAssets._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseAnalyzeOrgPolicyGovernedAssets, + "_BaseAnalyzeOrgPolicyGovernedAssets__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1850,12 +1873,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseAnalyzeOrgPolicyGovernedContainers._get_http_options() - request, metadata = self._interceptor.pre_analyze_org_policy_governed_containers(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseAnalyzeOrgPolicyGovernedContainers._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseAnalyzeOrgPolicyGovernedContainers._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseAnalyzeOrgPolicyGovernedContainers, + "_BaseAnalyzeOrgPolicyGovernedContainers__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1969,12 +1997,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseBatchGetAssetsHistory._get_http_options() - request, metadata = self._interceptor.pre_batch_get_assets_history(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseBatchGetAssetsHistory._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseBatchGetAssetsHistory._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseBatchGetAssetsHistory, + "_BaseBatchGetAssetsHistory__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2092,12 +2125,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseBatchGetEffectiveIamPolicies._get_http_options() - request, metadata = self._interceptor.pre_batch_get_effective_iam_policies(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseBatchGetEffectiveIamPolicies._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseBatchGetEffectiveIamPolicies._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseBatchGetEffectiveIamPolicies, + "_BaseBatchGetEffectiveIamPolicies__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2220,14 +2258,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseCreateFeed._get_http_options() - request, metadata = self._interceptor.pre_create_feed(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseCreateFeed._get_transcoded_request(http_options, request) - - body = _BaseAssetServiceRestTransport._BaseCreateFeed._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseCreateFeed._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseCreateFeed, + "_BaseCreateFeed__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2344,14 +2385,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseCreateSavedQuery._get_http_options() - request, metadata = self._interceptor.pre_create_saved_query(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseCreateSavedQuery._get_transcoded_request(http_options, request) - - body = _BaseAssetServiceRestTransport._BaseCreateSavedQuery._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseCreateSavedQuery._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseCreateSavedQuery, + "_BaseCreateSavedQuery__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2461,12 +2505,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseDeleteFeed._get_http_options() - request, metadata = self._interceptor.pre_delete_feed(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseDeleteFeed._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseDeleteFeed._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseDeleteFeed, + "_BaseDeleteFeed__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2546,12 +2595,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseDeleteSavedQuery._get_http_options() - request, metadata = self._interceptor.pre_delete_saved_query(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseDeleteSavedQuery._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseDeleteSavedQuery._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseDeleteSavedQuery, + "_BaseDeleteSavedQuery__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2639,14 +2693,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseExportAssets._get_http_options() - request, metadata = self._interceptor.pre_export_assets(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseExportAssets._get_transcoded_request(http_options, request) - - body = _BaseAssetServiceRestTransport._BaseExportAssets._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseExportAssets._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseExportAssets, + "_BaseExportAssets__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2766,12 +2823,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseGetFeed._get_http_options() - request, metadata = self._interceptor.pre_get_feed(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseGetFeed._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseGetFeed._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseGetFeed, + "_BaseGetFeed__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2887,12 +2949,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseGetSavedQuery._get_http_options() - request, metadata = self._interceptor.pre_get_saved_query(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseGetSavedQuery._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseGetSavedQuery._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseGetSavedQuery, + "_BaseGetSavedQuery__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -3006,12 +3073,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseListAssets._get_http_options() - request, metadata = self._interceptor.pre_list_assets(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseListAssets._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseListAssets._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseListAssets, + "_BaseListAssets__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -3125,12 +3197,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseListFeeds._get_http_options() - request, metadata = self._interceptor.pre_list_feeds(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseListFeeds._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseListFeeds._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseListFeeds, + "_BaseListFeeds__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -3244,12 +3321,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseListSavedQueries._get_http_options() - request, metadata = self._interceptor.pre_list_saved_queries(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseListSavedQueries._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseListSavedQueries._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseListSavedQueries, + "_BaseListSavedQueries__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -3364,14 +3446,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseQueryAssets._get_http_options() - request, metadata = self._interceptor.pre_query_assets(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseQueryAssets._get_transcoded_request(http_options, request) - - body = _BaseAssetServiceRestTransport._BaseQueryAssets._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseQueryAssets._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseQueryAssets, + "_BaseQueryAssets__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -3485,12 +3570,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseSearchAllIamPolicies._get_http_options() - request, metadata = self._interceptor.pre_search_all_iam_policies(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseSearchAllIamPolicies._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseSearchAllIamPolicies._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseSearchAllIamPolicies, + "_BaseSearchAllIamPolicies__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -3604,12 +3694,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseSearchAllResources._get_http_options() - request, metadata = self._interceptor.pre_search_all_resources(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseSearchAllResources._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseSearchAllResources._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseSearchAllResources, + "_BaseSearchAllResources__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -3732,14 +3827,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseUpdateFeed._get_http_options() - request, metadata = self._interceptor.pre_update_feed(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseUpdateFeed._get_transcoded_request(http_options, request) - - body = _BaseAssetServiceRestTransport._BaseUpdateFeed._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseUpdateFeed._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseUpdateFeed, + "_BaseUpdateFeed__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -3856,14 +3954,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseUpdateSavedQuery._get_http_options() - request, metadata = self._interceptor.pre_update_saved_query(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseUpdateSavedQuery._get_transcoded_request(http_options, request) - - body = _BaseAssetServiceRestTransport._BaseUpdateSavedQuery._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseUpdateSavedQuery._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseUpdateSavedQuery, + "_BaseUpdateSavedQuery__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -4165,12 +4266,17 @@ def __call__(self, """ http_options = _BaseAssetServiceRestTransport._BaseGetOperation._get_http_options() - request, metadata = self._interceptor.pre_get_operation(request, metadata) - transcoded_request = _BaseAssetServiceRestTransport._BaseGetOperation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseAssetServiceRestTransport._BaseGetOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseAssetServiceRestTransport._BaseGetOperation, + "_BaseGetOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) diff --git a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/rest_base.py b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/rest_base.py index f635d0015fee..175f9c010f33 100755 --- a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/rest_base.py +++ b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/rest_base.py @@ -97,7 +97,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -107,22 +106,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.AnalyzeIamPolicyRequest.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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseAnalyzeIamPolicy._get_unset_required_fields(query_params)) - - return query_params - class _BaseAnalyzeIamPolicyLongrunning: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -133,7 +116,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -144,31 +126,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.AnalyzeIamPolicyLongrunningRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseAnalyzeIamPolicyLongrunning._get_unset_required_fields(query_params)) - - return query_params - class _BaseAnalyzeMove: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -179,7 +136,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -189,22 +145,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.AnalyzeMoveRequest.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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseAnalyzeMove._get_unset_required_fields(query_params)) - - return query_params - class _BaseAnalyzeOrgPolicies: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -215,7 +155,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -225,22 +164,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.AnalyzeOrgPoliciesRequest.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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseAnalyzeOrgPolicies._get_unset_required_fields(query_params)) - - return query_params - class _BaseAnalyzeOrgPolicyGovernedAssets: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -251,7 +174,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -261,22 +183,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.AnalyzeOrgPolicyGovernedAssetsRequest.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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseAnalyzeOrgPolicyGovernedAssets._get_unset_required_fields(query_params)) - - return query_params - class _BaseAnalyzeOrgPolicyGovernedContainers: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -287,7 +193,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -297,22 +202,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.AnalyzeOrgPolicyGovernedContainersRequest.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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseAnalyzeOrgPolicyGovernedContainers._get_unset_required_fields(query_params)) - - return query_params - class _BaseBatchGetAssetsHistory: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -323,7 +212,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -333,22 +221,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.BatchGetAssetsHistoryRequest.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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseBatchGetAssetsHistory._get_unset_required_fields(query_params)) - - return query_params - class _BaseBatchGetEffectiveIamPolicies: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -359,7 +231,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -369,22 +240,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.BatchGetEffectiveIamPoliciesRequest.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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseBatchGetEffectiveIamPolicies._get_unset_required_fields(query_params)) - - return query_params - class _BaseCreateFeed: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -395,7 +250,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -406,31 +260,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.CreateFeedRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseCreateFeed._get_unset_required_fields(query_params)) - - return query_params - class _BaseCreateSavedQuery: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -441,7 +270,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -452,31 +280,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.CreateSavedQueryRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseCreateSavedQuery._get_unset_required_fields(query_params)) - - return query_params - class _BaseDeleteFeed: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -487,7 +290,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -497,22 +299,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.DeleteFeedRequest.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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseDeleteFeed._get_unset_required_fields(query_params)) - - return query_params - class _BaseDeleteSavedQuery: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -523,7 +309,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -533,22 +318,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.DeleteSavedQueryRequest.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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseDeleteSavedQuery._get_unset_required_fields(query_params)) - - return query_params - class _BaseExportAssets: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -559,7 +328,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -570,31 +338,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.ExportAssetsRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseExportAssets._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetFeed: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -605,7 +348,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -615,22 +357,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.GetFeedRequest.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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseGetFeed._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetSavedQuery: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -641,7 +367,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -651,22 +376,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.GetSavedQueryRequest.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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseGetSavedQuery._get_unset_required_fields(query_params)) - - return query_params - class _BaseListAssets: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -677,7 +386,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -687,22 +395,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.ListAssetsRequest.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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseListAssets._get_unset_required_fields(query_params)) - - return query_params - class _BaseListFeeds: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -713,7 +405,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -723,22 +414,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.ListFeedsRequest.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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseListFeeds._get_unset_required_fields(query_params)) - - return query_params - class _BaseListSavedQueries: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -749,7 +424,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -759,22 +433,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.ListSavedQueriesRequest.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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseListSavedQueries._get_unset_required_fields(query_params)) - - return query_params - class _BaseQueryAssets: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -785,7 +443,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -796,31 +453,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.QueryAssetsRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseQueryAssets._get_unset_required_fields(query_params)) - - return query_params - class _BaseSearchAllIamPolicies: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -831,7 +463,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -841,22 +472,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.SearchAllIamPoliciesRequest.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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseSearchAllIamPolicies._get_unset_required_fields(query_params)) - - return query_params - class _BaseSearchAllResources: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -867,7 +482,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -877,22 +491,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.SearchAllResourcesRequest.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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseSearchAllResources._get_unset_required_fields(query_params)) - - return query_params - class _BaseUpdateFeed: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -903,7 +501,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -914,31 +511,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.UpdateFeedRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseUpdateFeed._get_unset_required_fields(query_params)) - - return query_params - class _BaseUpdateSavedQuery: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -949,7 +521,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -960,32 +531,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = asset_service.UpdateSavedQueryRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseAssetServiceRestTransport._BaseUpdateSavedQuery._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetOperation: + """Base class for GetOperation REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -998,18 +545,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - __all__=( '_BaseAssetServiceRestTransport', diff --git a/packages/gapic-generator/tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py b/packages/gapic-generator/tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py index ea110a38acc3..1a367689d14e 100755 --- a/packages/gapic-generator/tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py +++ b/packages/gapic-generator/tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py @@ -700,7 +700,7 @@ def test_asset_service_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -747,7 +747,7 @@ def test_asset_service_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): diff --git a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py new file mode 100644 index 000000000000..6f17d9fcc3ea --- /dev/null +++ b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py @@ -0,0 +1,314 @@ +# # Copyright 2026 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. +# +"""A compatibility module for older versions of google-api-core.""" + +import functools +import json +import operator +import os +import re +import uuid +from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from google.auth.exceptions import MutualTLSChannelError +import google.protobuf.message + + +try: + from google.api_core.universe import ( + get_default_mtls_endpoint, + get_api_endpoint, + get_universe_domain, + ) +except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]: + """Converts api endpoint to mTLS 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) + if m is None: + # Could not parse api_endpoint; return as-is. + return 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") + + def get_api_endpoint( + api_override: Optional[str], + client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + universe_domain: str, + use_mtls_endpoint: str, + default_universe: str, + default_mtls_endpoint: Optional[str], + default_endpoint_template: str, + ) -> Optional[str]: + """Return the API endpoint 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): + if universe_domain != default_universe: + raise MutualTLSChannelError( + f"mTLS is not supported in any universe other than {default_universe}." + ) + api_endpoint = default_mtls_endpoint + else: + api_endpoint = default_endpoint_template.format(UNIVERSE_DOMAIN=universe_domain) + return api_endpoint + + def get_universe_domain( + client_universe_domain: Optional[str], + universe_domain_env: Optional[str], + default_universe: str, + ) -> str: + """Return the universe domain used by the client.""" + universe_domain = 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 + + +try: + from google.api_core.gapic_v1.config import ( + use_client_cert_effective, + get_client_cert_source, + read_environment_variables, + ) +except ImportError: + from google.auth.transport import mtls # type: ignore + + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + + def use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + if hasattr(mtls, "should_use_client_cert"): + return mtls.should_use_client_cert() + else: + use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() + if use_client_cert_str not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" + " either `true` or `false`" + ) + return use_client_cert_str == "true" + + def get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return 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 ( + hasattr(mtls, "has_default_client_cert_source") + and mtls.has_default_client_cert_source() + ): + client_cert_source = mtls.default_client_cert_source() + else: + raise ValueError( + "Client certificate is required for mTLS, but no client certificate source was provided or found." + ) + return client_cert_source + + def read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + use_client_cert = use_client_cert_effective() + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() + universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") + 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, use_mtls_endpoint, universe_domain_env + + +try: + from google.api_core.gapic_v1.request import setup_request_id # type: ignore +except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. + def setup_request_id(request, field_name: str, is_proto3_optional: bool): + """Populate a UUID4 field in the request if it is not already set. + + Args: + request (Union[google.protobuf.message.Message, dict]): The request object. + field_name (str): The name of the field to populate. + is_proto3_optional (bool): Whether the field is proto3 optional. + """ + request_id_val = str(uuid.uuid4()) + if request is None: + return + + if isinstance(request, dict): + if is_proto3_optional: + if field_name not in request or request[field_name] is None: + request[field_name] = request_id_val + elif not request.get(field_name): + request[field_name] = request_id_val + return + + if is_proto3_optional: + try: + # Pure protobuf messages + if not request.HasField(field_name): + setattr(request, field_name, request_id_val) + except (AttributeError, ValueError): + # Proto-plus messages or other objects + if getattr(request, field_name, None) is None: + setattr(request, field_name, request_id_val) + else: + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) + + +try: + from google.api_core.rest_helpers import ( # type: ignore + flatten_query_params, + transcode_request as _core_transcode_request, + ) + import inspect + if "rest_numeric_enums" not in inspect.signature(_core_transcode_request).parameters: + raise ImportError + transcode_request = _core_transcode_request +except (ImportError, AttributeError): # pragma: NO COVER + # TODO: Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + from google.protobuf import json_format # type: ignore + from google.api_core import path_template # type: ignore + + def flatten_query_params(obj, strict=False): # pragma: NO COVER + if obj is not None and not isinstance(obj, dict): + raise TypeError("flatten_query_params must be called with dict object") + return _flatten(obj, key_path=[], strict=strict) + + def _flatten(obj, key_path, strict=False): # pragma: NO COVER + if obj is None: + return [] + if isinstance(obj, dict): + return _flatten_dict(obj, key_path=key_path, strict=strict) + if isinstance(obj, list): + return _flatten_list(obj, key_path=key_path, strict=strict) + return _flatten_value(obj, key_path=key_path, strict=strict) + + def _is_primitive_value(obj): # pragma: NO COVER + if obj is None: + return False + if isinstance(obj, (list, dict)): + raise ValueError("query params may not contain repeated dicts or lists") + return True + + def _flatten_value(obj, key_path, strict=False): # pragma: NO COVER + return [(".".join(key_path), _canonicalize(obj, strict=strict))] + + def _flatten_dict(obj, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten(value, key_path=key_path + [key], strict=strict) + for key, value in obj.items() + ) + return functools.reduce(operator.concat, items, []) # type: ignore + + def _flatten_list(elems, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten_value(elem, key_path=key_path, strict=strict) + for elem in elems + if _is_primitive_value(elem) + ) + return functools.reduce(operator.concat, items, []) # type: ignore + + def _canonicalize(obj, strict=False): # pragma: NO COVER + if strict: + value = str(obj) + if isinstance(obj, bool): + value = value.lower() + return value + return obj + + def transcode_request( # pragma: NO COVER + http_options: List[Dict[str, str]], + request: Any, + required_fields_default_values: Optional[Dict[str, Any]] = None, + rest_numeric_enums: bool = False, + ) -> Tuple[Dict[str, Any], Optional[str], Dict[str, Any]]: + pb_request = getattr(request, "_pb", request) + transcoded_request = path_template.transcode(http_options, pb_request) + + body_json = None + if transcoded_request.get("body") is not None: + body_json = json_format.MessageToJson( + transcoded_request["body"], + use_integers_for_enums=rest_numeric_enums, + ) + + query_params_json = {} + if transcoded_request.get("query_params") is not None: + query_params_json = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + use_integers_for_enums=rest_numeric_enums, + ) + ) + + if required_fields_default_values: + matched_option = None + for option in http_options: + if option.get("method", "").lower() == transcoded_request.get("method", "").lower(): + if path_template.validate( + option.get("uri", ""), transcoded_request.get("uri", "") + ): + matched_option = option + break + + bound_fields = set() + if matched_option: + uri_template = matched_option.get("uri", "") + for m in path_template._VARIABLE_RE.finditer(uri_template): + name = m.group("name") + if name: + bound_fields.add(name.split(".")[0]) + body_param = matched_option.get("body") + if body_param: + if body_param == "*": + bound_fields = None + else: + bound_fields.add(body_param.split(".")[0]) + + if bound_fields is not None: + for k, v in required_fields_default_values.items(): + if k in bound_fields: + continue + if k not in query_params_json: + query_params_json[k] = v + + if rest_numeric_enums: + query_params_json["$alt"] = "json;enum-encoding=int" + + return transcoded_request, body_json, query_params_json diff --git a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/transports/rest.py b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/transports/rest.py index f4969132838a..77573563b286 100755 --- a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/transports/rest.py +++ b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/transports/rest.py @@ -20,7 +20,7 @@ from google.auth import credentials as ga_credentials # type: ignore from google.api_core import exceptions as core_exceptions from google.api_core import retry as retries -from google.api_core import rest_helpers +from google.iam.credentials_v1 import _compat as rest_helpers from google.api_core import rest_streaming from google.api_core import gapic_v1 import google.protobuf @@ -414,14 +414,17 @@ def __call__(self, """ http_options = _BaseIAMCredentialsRestTransport._BaseGenerateAccessToken._get_http_options() - request, metadata = self._interceptor.pre_generate_access_token(request, metadata) - transcoded_request = _BaseIAMCredentialsRestTransport._BaseGenerateAccessToken._get_transcoded_request(http_options, request) - - body = _BaseIAMCredentialsRestTransport._BaseGenerateAccessToken._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseIAMCredentialsRestTransport._BaseGenerateAccessToken._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseIAMCredentialsRestTransport._BaseGenerateAccessToken, + "_BaseGenerateAccessToken__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -536,14 +539,17 @@ def __call__(self, """ http_options = _BaseIAMCredentialsRestTransport._BaseGenerateIdToken._get_http_options() - request, metadata = self._interceptor.pre_generate_id_token(request, metadata) - transcoded_request = _BaseIAMCredentialsRestTransport._BaseGenerateIdToken._get_transcoded_request(http_options, request) - - body = _BaseIAMCredentialsRestTransport._BaseGenerateIdToken._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseIAMCredentialsRestTransport._BaseGenerateIdToken._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseIAMCredentialsRestTransport._BaseGenerateIdToken, + "_BaseGenerateIdToken__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -658,14 +664,17 @@ def __call__(self, """ http_options = _BaseIAMCredentialsRestTransport._BaseSignBlob._get_http_options() - request, metadata = self._interceptor.pre_sign_blob(request, metadata) - transcoded_request = _BaseIAMCredentialsRestTransport._BaseSignBlob._get_transcoded_request(http_options, request) - - body = _BaseIAMCredentialsRestTransport._BaseSignBlob._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseIAMCredentialsRestTransport._BaseSignBlob._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseIAMCredentialsRestTransport._BaseSignBlob, + "_BaseSignBlob__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -780,14 +789,17 @@ def __call__(self, """ http_options = _BaseIAMCredentialsRestTransport._BaseSignJwt._get_http_options() - request, metadata = self._interceptor.pre_sign_jwt(request, metadata) - transcoded_request = _BaseIAMCredentialsRestTransport._BaseSignJwt._get_transcoded_request(http_options, request) - - body = _BaseIAMCredentialsRestTransport._BaseSignJwt._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseIAMCredentialsRestTransport._BaseSignJwt._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseIAMCredentialsRestTransport._BaseSignJwt, + "_BaseSignJwt__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) diff --git a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/transports/rest_base.py b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/transports/rest_base.py index db587944901a..5619f62ae96a 100755 --- a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/transports/rest_base.py +++ b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/transports/rest_base.py @@ -95,7 +95,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -106,31 +105,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = common.GenerateAccessTokenRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseIAMCredentialsRestTransport._BaseGenerateAccessToken._get_unset_required_fields(query_params)) - - return query_params - class _BaseGenerateIdToken: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -141,7 +115,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -152,31 +125,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = common.GenerateIdTokenRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseIAMCredentialsRestTransport._BaseGenerateIdToken._get_unset_required_fields(query_params)) - - return query_params - class _BaseSignBlob: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -187,7 +135,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -198,31 +145,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = common.SignBlobRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseIAMCredentialsRestTransport._BaseSignBlob._get_unset_required_fields(query_params)) - - return query_params - class _BaseSignJwt: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -233,7 +155,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -244,31 +165,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = common.SignJwtRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseIAMCredentialsRestTransport._BaseSignJwt._get_unset_required_fields(query_params)) - - return query_params - __all__=( '_BaseIAMCredentialsRestTransport', diff --git a/packages/gapic-generator/tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py b/packages/gapic-generator/tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py index 14a6074a40f9..508e17a7e889 100755 --- a/packages/gapic-generator/tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py +++ b/packages/gapic-generator/tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py @@ -690,7 +690,7 @@ def test_iam_credentials_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -737,7 +737,7 @@ def test_iam_credentials_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): diff --git a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py new file mode 100644 index 000000000000..6f17d9fcc3ea --- /dev/null +++ b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py @@ -0,0 +1,314 @@ +# # Copyright 2026 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. +# +"""A compatibility module for older versions of google-api-core.""" + +import functools +import json +import operator +import os +import re +import uuid +from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from google.auth.exceptions import MutualTLSChannelError +import google.protobuf.message + + +try: + from google.api_core.universe import ( + get_default_mtls_endpoint, + get_api_endpoint, + get_universe_domain, + ) +except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]: + """Converts api endpoint to mTLS 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) + if m is None: + # Could not parse api_endpoint; return as-is. + return 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") + + def get_api_endpoint( + api_override: Optional[str], + client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + universe_domain: str, + use_mtls_endpoint: str, + default_universe: str, + default_mtls_endpoint: Optional[str], + default_endpoint_template: str, + ) -> Optional[str]: + """Return the API endpoint 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): + if universe_domain != default_universe: + raise MutualTLSChannelError( + f"mTLS is not supported in any universe other than {default_universe}." + ) + api_endpoint = default_mtls_endpoint + else: + api_endpoint = default_endpoint_template.format(UNIVERSE_DOMAIN=universe_domain) + return api_endpoint + + def get_universe_domain( + client_universe_domain: Optional[str], + universe_domain_env: Optional[str], + default_universe: str, + ) -> str: + """Return the universe domain used by the client.""" + universe_domain = 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 + + +try: + from google.api_core.gapic_v1.config import ( + use_client_cert_effective, + get_client_cert_source, + read_environment_variables, + ) +except ImportError: + from google.auth.transport import mtls # type: ignore + + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + + def use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + if hasattr(mtls, "should_use_client_cert"): + return mtls.should_use_client_cert() + else: + use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() + if use_client_cert_str not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" + " either `true` or `false`" + ) + return use_client_cert_str == "true" + + def get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return 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 ( + hasattr(mtls, "has_default_client_cert_source") + and mtls.has_default_client_cert_source() + ): + client_cert_source = mtls.default_client_cert_source() + else: + raise ValueError( + "Client certificate is required for mTLS, but no client certificate source was provided or found." + ) + return client_cert_source + + def read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + use_client_cert = use_client_cert_effective() + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() + universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") + 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, use_mtls_endpoint, universe_domain_env + + +try: + from google.api_core.gapic_v1.request import setup_request_id # type: ignore +except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. + def setup_request_id(request, field_name: str, is_proto3_optional: bool): + """Populate a UUID4 field in the request if it is not already set. + + Args: + request (Union[google.protobuf.message.Message, dict]): The request object. + field_name (str): The name of the field to populate. + is_proto3_optional (bool): Whether the field is proto3 optional. + """ + request_id_val = str(uuid.uuid4()) + if request is None: + return + + if isinstance(request, dict): + if is_proto3_optional: + if field_name not in request or request[field_name] is None: + request[field_name] = request_id_val + elif not request.get(field_name): + request[field_name] = request_id_val + return + + if is_proto3_optional: + try: + # Pure protobuf messages + if not request.HasField(field_name): + setattr(request, field_name, request_id_val) + except (AttributeError, ValueError): + # Proto-plus messages or other objects + if getattr(request, field_name, None) is None: + setattr(request, field_name, request_id_val) + else: + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) + + +try: + from google.api_core.rest_helpers import ( # type: ignore + flatten_query_params, + transcode_request as _core_transcode_request, + ) + import inspect + if "rest_numeric_enums" not in inspect.signature(_core_transcode_request).parameters: + raise ImportError + transcode_request = _core_transcode_request +except (ImportError, AttributeError): # pragma: NO COVER + # TODO: Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + from google.protobuf import json_format # type: ignore + from google.api_core import path_template # type: ignore + + def flatten_query_params(obj, strict=False): # pragma: NO COVER + if obj is not None and not isinstance(obj, dict): + raise TypeError("flatten_query_params must be called with dict object") + return _flatten(obj, key_path=[], strict=strict) + + def _flatten(obj, key_path, strict=False): # pragma: NO COVER + if obj is None: + return [] + if isinstance(obj, dict): + return _flatten_dict(obj, key_path=key_path, strict=strict) + if isinstance(obj, list): + return _flatten_list(obj, key_path=key_path, strict=strict) + return _flatten_value(obj, key_path=key_path, strict=strict) + + def _is_primitive_value(obj): # pragma: NO COVER + if obj is None: + return False + if isinstance(obj, (list, dict)): + raise ValueError("query params may not contain repeated dicts or lists") + return True + + def _flatten_value(obj, key_path, strict=False): # pragma: NO COVER + return [(".".join(key_path), _canonicalize(obj, strict=strict))] + + def _flatten_dict(obj, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten(value, key_path=key_path + [key], strict=strict) + for key, value in obj.items() + ) + return functools.reduce(operator.concat, items, []) # type: ignore + + def _flatten_list(elems, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten_value(elem, key_path=key_path, strict=strict) + for elem in elems + if _is_primitive_value(elem) + ) + return functools.reduce(operator.concat, items, []) # type: ignore + + def _canonicalize(obj, strict=False): # pragma: NO COVER + if strict: + value = str(obj) + if isinstance(obj, bool): + value = value.lower() + return value + return obj + + def transcode_request( # pragma: NO COVER + http_options: List[Dict[str, str]], + request: Any, + required_fields_default_values: Optional[Dict[str, Any]] = None, + rest_numeric_enums: bool = False, + ) -> Tuple[Dict[str, Any], Optional[str], Dict[str, Any]]: + pb_request = getattr(request, "_pb", request) + transcoded_request = path_template.transcode(http_options, pb_request) + + body_json = None + if transcoded_request.get("body") is not None: + body_json = json_format.MessageToJson( + transcoded_request["body"], + use_integers_for_enums=rest_numeric_enums, + ) + + query_params_json = {} + if transcoded_request.get("query_params") is not None: + query_params_json = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + use_integers_for_enums=rest_numeric_enums, + ) + ) + + if required_fields_default_values: + matched_option = None + for option in http_options: + if option.get("method", "").lower() == transcoded_request.get("method", "").lower(): + if path_template.validate( + option.get("uri", ""), transcoded_request.get("uri", "") + ): + matched_option = option + break + + bound_fields = set() + if matched_option: + uri_template = matched_option.get("uri", "") + for m in path_template._VARIABLE_RE.finditer(uri_template): + name = m.group("name") + if name: + bound_fields.add(name.split(".")[0]) + body_param = matched_option.get("body") + if body_param: + if body_param == "*": + bound_fields = None + else: + bound_fields.add(body_param.split(".")[0]) + + if bound_fields is not None: + for k, v in required_fields_default_values.items(): + if k in bound_fields: + continue + if k not in query_params_json: + query_params_json[k] = v + + if rest_numeric_enums: + query_params_json["$alt"] = "json;enum-encoding=int" + + return transcoded_request, body_json, query_params_json diff --git a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/transports/rest.py b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/transports/rest.py index fb9a5c2a8c26..a18814177a38 100755 --- a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/transports/rest.py +++ b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/transports/rest.py @@ -20,7 +20,7 @@ from google.auth import credentials as ga_credentials # type: ignore from google.api_core import exceptions as core_exceptions from google.api_core import retry as retries -from google.api_core import rest_helpers +from google.cloud.eventarc_v1 import _compat as rest_helpers from google.api_core import rest_streaming from google.api_core import gapic_v1 import google.protobuf @@ -2206,14 +2206,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseCreateChannel._get_http_options() - request, metadata = self._interceptor.pre_create_channel(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseCreateChannel._get_transcoded_request(http_options, request) - - body = _BaseEventarcRestTransport._BaseCreateChannel._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseCreateChannel._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseCreateChannel, + "_BaseCreateChannel__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2330,14 +2333,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseCreateChannelConnection._get_http_options() - request, metadata = self._interceptor.pre_create_channel_connection(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseCreateChannelConnection._get_transcoded_request(http_options, request) - - body = _BaseEventarcRestTransport._BaseCreateChannelConnection._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseCreateChannelConnection._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseCreateChannelConnection, + "_BaseCreateChannelConnection__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2454,14 +2460,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseCreateEnrollment._get_http_options() - request, metadata = self._interceptor.pre_create_enrollment(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseCreateEnrollment._get_transcoded_request(http_options, request) - - body = _BaseEventarcRestTransport._BaseCreateEnrollment._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseCreateEnrollment._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseCreateEnrollment, + "_BaseCreateEnrollment__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2578,14 +2587,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseCreateGoogleApiSource._get_http_options() - request, metadata = self._interceptor.pre_create_google_api_source(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseCreateGoogleApiSource._get_transcoded_request(http_options, request) - - body = _BaseEventarcRestTransport._BaseCreateGoogleApiSource._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseCreateGoogleApiSource._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseCreateGoogleApiSource, + "_BaseCreateGoogleApiSource__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2702,14 +2714,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseCreateMessageBus._get_http_options() - request, metadata = self._interceptor.pre_create_message_bus(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseCreateMessageBus._get_transcoded_request(http_options, request) - - body = _BaseEventarcRestTransport._BaseCreateMessageBus._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseCreateMessageBus._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseCreateMessageBus, + "_BaseCreateMessageBus__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2826,14 +2841,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseCreatePipeline._get_http_options() - request, metadata = self._interceptor.pre_create_pipeline(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseCreatePipeline._get_transcoded_request(http_options, request) - - body = _BaseEventarcRestTransport._BaseCreatePipeline._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseCreatePipeline._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseCreatePipeline, + "_BaseCreatePipeline__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2950,14 +2968,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseCreateTrigger._get_http_options() - request, metadata = self._interceptor.pre_create_trigger(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseCreateTrigger._get_transcoded_request(http_options, request) - - body = _BaseEventarcRestTransport._BaseCreateTrigger._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseCreateTrigger._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseCreateTrigger, + "_BaseCreateTrigger__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -3073,12 +3094,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseDeleteChannel._get_http_options() - request, metadata = self._interceptor.pre_delete_channel(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseDeleteChannel._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseDeleteChannel._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseDeleteChannel, + "_BaseDeleteChannel__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -3194,12 +3220,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseDeleteChannelConnection._get_http_options() - request, metadata = self._interceptor.pre_delete_channel_connection(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseDeleteChannelConnection._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseDeleteChannelConnection._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseDeleteChannelConnection, + "_BaseDeleteChannelConnection__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -3315,12 +3346,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseDeleteEnrollment._get_http_options() - request, metadata = self._interceptor.pre_delete_enrollment(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseDeleteEnrollment._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseDeleteEnrollment._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseDeleteEnrollment, + "_BaseDeleteEnrollment__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -3436,12 +3472,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseDeleteGoogleApiSource._get_http_options() - request, metadata = self._interceptor.pre_delete_google_api_source(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseDeleteGoogleApiSource._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseDeleteGoogleApiSource._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseDeleteGoogleApiSource, + "_BaseDeleteGoogleApiSource__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -3557,12 +3598,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseDeleteMessageBus._get_http_options() - request, metadata = self._interceptor.pre_delete_message_bus(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseDeleteMessageBus._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseDeleteMessageBus._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseDeleteMessageBus, + "_BaseDeleteMessageBus__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -3678,12 +3724,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseDeletePipeline._get_http_options() - request, metadata = self._interceptor.pre_delete_pipeline(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseDeletePipeline._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseDeletePipeline._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseDeletePipeline, + "_BaseDeletePipeline__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -3799,12 +3850,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseDeleteTrigger._get_http_options() - request, metadata = self._interceptor.pre_delete_trigger(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseDeleteTrigger._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseDeleteTrigger._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseDeleteTrigger, + "_BaseDeleteTrigger__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -3925,12 +3981,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseGetChannel._get_http_options() - request, metadata = self._interceptor.pre_get_channel(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseGetChannel._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseGetChannel._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseGetChannel, + "_BaseGetChannel__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -4052,12 +4113,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseGetChannelConnection._get_http_options() - request, metadata = self._interceptor.pre_get_channel_connection(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseGetChannelConnection._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseGetChannelConnection._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseGetChannelConnection, + "_BaseGetChannelConnection__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -4178,12 +4244,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseGetEnrollment._get_http_options() - request, metadata = self._interceptor.pre_get_enrollment(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseGetEnrollment._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseGetEnrollment._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseGetEnrollment, + "_BaseGetEnrollment__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -4301,12 +4372,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseGetGoogleApiSource._get_http_options() - request, metadata = self._interceptor.pre_get_google_api_source(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseGetGoogleApiSource._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseGetGoogleApiSource._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseGetGoogleApiSource, + "_BaseGetGoogleApiSource__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -4429,12 +4505,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseGetGoogleChannelConfig._get_http_options() - request, metadata = self._interceptor.pre_get_google_channel_config(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseGetGoogleChannelConfig._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseGetGoogleChannelConfig._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseGetGoogleChannelConfig, + "_BaseGetGoogleChannelConfig__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -4557,12 +4638,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseGetMessageBus._get_http_options() - request, metadata = self._interceptor.pre_get_message_bus(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseGetMessageBus._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseGetMessageBus._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseGetMessageBus, + "_BaseGetMessageBus__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -4679,12 +4765,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseGetPipeline._get_http_options() - request, metadata = self._interceptor.pre_get_pipeline(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseGetPipeline._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseGetPipeline._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseGetPipeline, + "_BaseGetPipeline__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -4801,12 +4892,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseGetProvider._get_http_options() - request, metadata = self._interceptor.pre_get_provider(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseGetProvider._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseGetProvider._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseGetProvider, + "_BaseGetProvider__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -4923,12 +5019,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseGetTrigger._get_http_options() - request, metadata = self._interceptor.pre_get_trigger(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseGetTrigger._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseGetTrigger._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseGetTrigger, + "_BaseGetTrigger__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -5045,12 +5146,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseListChannelConnections._get_http_options() - request, metadata = self._interceptor.pre_list_channel_connections(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseListChannelConnections._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseListChannelConnections._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseListChannelConnections, + "_BaseListChannelConnections__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -5165,12 +5271,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseListChannels._get_http_options() - request, metadata = self._interceptor.pre_list_channels(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseListChannels._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseListChannels._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseListChannels, + "_BaseListChannels__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -5285,12 +5396,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseListEnrollments._get_http_options() - request, metadata = self._interceptor.pre_list_enrollments(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseListEnrollments._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseListEnrollments._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseListEnrollments, + "_BaseListEnrollments__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -5407,12 +5523,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseListGoogleApiSources._get_http_options() - request, metadata = self._interceptor.pre_list_google_api_sources(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseListGoogleApiSources._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseListGoogleApiSources._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseListGoogleApiSources, + "_BaseListGoogleApiSources__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -5530,12 +5651,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseListMessageBusEnrollments._get_http_options() - request, metadata = self._interceptor.pre_list_message_bus_enrollments(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseListMessageBusEnrollments._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseListMessageBusEnrollments._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseListMessageBusEnrollments, + "_BaseListMessageBusEnrollments__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -5652,12 +5778,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseListMessageBuses._get_http_options() - request, metadata = self._interceptor.pre_list_message_buses(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseListMessageBuses._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseListMessageBuses._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseListMessageBuses, + "_BaseListMessageBuses__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -5774,12 +5905,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseListPipelines._get_http_options() - request, metadata = self._interceptor.pre_list_pipelines(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseListPipelines._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseListPipelines._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseListPipelines, + "_BaseListPipelines__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -5894,12 +6030,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseListProviders._get_http_options() - request, metadata = self._interceptor.pre_list_providers(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseListProviders._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseListProviders._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseListProviders, + "_BaseListProviders__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -6014,12 +6155,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseListTriggers._get_http_options() - request, metadata = self._interceptor.pre_list_triggers(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseListTriggers._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseListTriggers._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseListTriggers, + "_BaseListTriggers__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -6138,14 +6284,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseUpdateChannel._get_http_options() - request, metadata = self._interceptor.pre_update_channel(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseUpdateChannel._get_transcoded_request(http_options, request) - - body = _BaseEventarcRestTransport._BaseUpdateChannel._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseUpdateChannel._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseUpdateChannel, + "_BaseUpdateChannel__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -6262,14 +6411,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseUpdateEnrollment._get_http_options() - request, metadata = self._interceptor.pre_update_enrollment(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseUpdateEnrollment._get_transcoded_request(http_options, request) - - body = _BaseEventarcRestTransport._BaseUpdateEnrollment._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseUpdateEnrollment._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseUpdateEnrollment, + "_BaseUpdateEnrollment__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -6386,14 +6538,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseUpdateGoogleApiSource._get_http_options() - request, metadata = self._interceptor.pre_update_google_api_source(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseUpdateGoogleApiSource._get_transcoded_request(http_options, request) - - body = _BaseEventarcRestTransport._BaseUpdateGoogleApiSource._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseUpdateGoogleApiSource._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseUpdateGoogleApiSource, + "_BaseUpdateGoogleApiSource__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -6516,14 +6671,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseUpdateGoogleChannelConfig._get_http_options() - request, metadata = self._interceptor.pre_update_google_channel_config(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseUpdateGoogleChannelConfig._get_transcoded_request(http_options, request) - - body = _BaseEventarcRestTransport._BaseUpdateGoogleChannelConfig._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseUpdateGoogleChannelConfig._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseUpdateGoogleChannelConfig, + "_BaseUpdateGoogleChannelConfig__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -6642,14 +6800,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseUpdateMessageBus._get_http_options() - request, metadata = self._interceptor.pre_update_message_bus(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseUpdateMessageBus._get_transcoded_request(http_options, request) - - body = _BaseEventarcRestTransport._BaseUpdateMessageBus._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseUpdateMessageBus._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseUpdateMessageBus, + "_BaseUpdateMessageBus__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -6766,14 +6927,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseUpdatePipeline._get_http_options() - request, metadata = self._interceptor.pre_update_pipeline(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseUpdatePipeline._get_transcoded_request(http_options, request) - - body = _BaseEventarcRestTransport._BaseUpdatePipeline._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseUpdatePipeline._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseUpdatePipeline, + "_BaseUpdatePipeline__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -6890,14 +7054,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseUpdateTrigger._get_http_options() - request, metadata = self._interceptor.pre_update_trigger(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseUpdateTrigger._get_transcoded_request(http_options, request) - - body = _BaseEventarcRestTransport._BaseUpdateTrigger._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseUpdateTrigger._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseUpdateTrigger, + "_BaseUpdateTrigger__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -7325,12 +7492,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseGetLocation._get_http_options() - request, metadata = self._interceptor.pre_get_location(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseGetLocation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseGetLocation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseGetLocation, + "_BaseGetLocation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -7443,12 +7615,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseListLocations._get_http_options() - request, metadata = self._interceptor.pre_list_locations(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseListLocations._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseListLocations._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseListLocations, + "_BaseListLocations__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -7561,12 +7738,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseGetIamPolicy._get_http_options() - request, metadata = self._interceptor.pre_get_iam_policy(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseGetIamPolicy._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseGetIamPolicy._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseGetIamPolicy, + "_BaseGetIamPolicy__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -7680,14 +7862,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseSetIamPolicy._get_http_options() - request, metadata = self._interceptor.pre_set_iam_policy(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseSetIamPolicy._get_transcoded_request(http_options, request) - - body = _BaseEventarcRestTransport._BaseSetIamPolicy._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseSetIamPolicy._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseSetIamPolicy, + "_BaseSetIamPolicy__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -7801,14 +7986,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseTestIamPermissions._get_http_options() - request, metadata = self._interceptor.pre_test_iam_permissions(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseTestIamPermissions._get_transcoded_request(http_options, request) - - body = _BaseEventarcRestTransport._BaseTestIamPermissions._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseTestIamPermissions._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseTestIamPermissions, + "_BaseTestIamPermissions__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -7919,14 +8107,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseCancelOperation._get_http_options() - request, metadata = self._interceptor.pre_cancel_operation(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseCancelOperation._get_transcoded_request(http_options, request) - - body = _BaseEventarcRestTransport._BaseCancelOperation._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseCancelOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseCancelOperation, + "_BaseCancelOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -8013,12 +8204,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseDeleteOperation._get_http_options() - request, metadata = self._interceptor.pre_delete_operation(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseDeleteOperation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseDeleteOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseDeleteOperation, + "_BaseDeleteOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -8108,12 +8304,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseGetOperation._get_http_options() - request, metadata = self._interceptor.pre_get_operation(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseGetOperation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseGetOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseGetOperation, + "_BaseGetOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -8226,12 +8427,17 @@ def __call__(self, """ http_options = _BaseEventarcRestTransport._BaseListOperations._get_http_options() - request, metadata = self._interceptor.pre_list_operations(request, metadata) - transcoded_request = _BaseEventarcRestTransport._BaseListOperations._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseEventarcRestTransport._BaseListOperations._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseEventarcRestTransport._BaseListOperations, + "_BaseListOperations__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) diff --git a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/transports/rest_base.py b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/transports/rest_base.py index 0405ac986903..d78e8158be41 100755 --- a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/transports/rest_base.py +++ b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/transports/rest_base.py @@ -109,7 +109,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -120,31 +119,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.CreateChannelRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseCreateChannel._get_unset_required_fields(query_params)) - - return query_params - class _BaseCreateChannelConnection: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -155,7 +129,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -166,31 +139,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.CreateChannelConnectionRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseCreateChannelConnection._get_unset_required_fields(query_params)) - - return query_params - class _BaseCreateEnrollment: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -201,7 +149,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -212,31 +159,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.CreateEnrollmentRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseCreateEnrollment._get_unset_required_fields(query_params)) - - return query_params - class _BaseCreateGoogleApiSource: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -247,7 +169,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -258,31 +179,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.CreateGoogleApiSourceRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseCreateGoogleApiSource._get_unset_required_fields(query_params)) - - return query_params - class _BaseCreateMessageBus: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -293,7 +189,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -304,31 +199,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.CreateMessageBusRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseCreateMessageBus._get_unset_required_fields(query_params)) - - return query_params - class _BaseCreatePipeline: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -339,7 +209,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -350,31 +219,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.CreatePipelineRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseCreatePipeline._get_unset_required_fields(query_params)) - - return query_params - class _BaseCreateTrigger: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -385,7 +229,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -396,31 +239,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.CreateTriggerRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseCreateTrigger._get_unset_required_fields(query_params)) - - return query_params - class _BaseDeleteChannel: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -431,7 +249,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -441,22 +258,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.DeleteChannelRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseDeleteChannel._get_unset_required_fields(query_params)) - - return query_params - class _BaseDeleteChannelConnection: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -467,7 +268,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -477,22 +277,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.DeleteChannelConnectionRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseDeleteChannelConnection._get_unset_required_fields(query_params)) - - return query_params - class _BaseDeleteEnrollment: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -503,7 +287,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -513,22 +296,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.DeleteEnrollmentRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseDeleteEnrollment._get_unset_required_fields(query_params)) - - return query_params - class _BaseDeleteGoogleApiSource: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -539,7 +306,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -549,22 +315,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.DeleteGoogleApiSourceRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseDeleteGoogleApiSource._get_unset_required_fields(query_params)) - - return query_params - class _BaseDeleteMessageBus: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -575,7 +325,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -585,22 +334,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.DeleteMessageBusRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseDeleteMessageBus._get_unset_required_fields(query_params)) - - return query_params - class _BaseDeletePipeline: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -611,7 +344,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -621,22 +353,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.DeletePipelineRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseDeletePipeline._get_unset_required_fields(query_params)) - - return query_params - class _BaseDeleteTrigger: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -647,7 +363,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -657,22 +372,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.DeleteTriggerRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseDeleteTrigger._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetChannel: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -683,7 +382,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -693,22 +391,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.GetChannelRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseGetChannel._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetChannelConnection: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -719,7 +401,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -729,22 +410,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.GetChannelConnectionRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseGetChannelConnection._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetEnrollment: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -755,7 +420,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -765,22 +429,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.GetEnrollmentRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseGetEnrollment._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetGoogleApiSource: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -791,7 +439,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -801,22 +448,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.GetGoogleApiSourceRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseGetGoogleApiSource._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetGoogleChannelConfig: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -827,7 +458,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -837,22 +467,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.GetGoogleChannelConfigRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseGetGoogleChannelConfig._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetMessageBus: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -863,7 +477,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -873,22 +486,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.GetMessageBusRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseGetMessageBus._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetPipeline: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -899,7 +496,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -909,22 +505,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.GetPipelineRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseGetPipeline._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetProvider: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -935,7 +515,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -945,22 +524,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.GetProviderRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseGetProvider._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetTrigger: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -971,7 +534,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -981,22 +543,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.GetTriggerRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseGetTrigger._get_unset_required_fields(query_params)) - - return query_params - class _BaseListChannelConnections: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1007,7 +553,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -1017,22 +562,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.ListChannelConnectionsRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseListChannelConnections._get_unset_required_fields(query_params)) - - return query_params - class _BaseListChannels: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1043,7 +572,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -1053,22 +581,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.ListChannelsRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseListChannels._get_unset_required_fields(query_params)) - - return query_params - class _BaseListEnrollments: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1079,7 +591,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -1089,22 +600,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.ListEnrollmentsRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseListEnrollments._get_unset_required_fields(query_params)) - - return query_params - class _BaseListGoogleApiSources: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1115,7 +610,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -1125,22 +619,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.ListGoogleApiSourcesRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseListGoogleApiSources._get_unset_required_fields(query_params)) - - return query_params - class _BaseListMessageBusEnrollments: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1151,7 +629,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -1161,22 +638,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.ListMessageBusEnrollmentsRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseListMessageBusEnrollments._get_unset_required_fields(query_params)) - - return query_params - class _BaseListMessageBuses: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1187,7 +648,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -1197,22 +657,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.ListMessageBusesRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseListMessageBuses._get_unset_required_fields(query_params)) - - return query_params - class _BaseListPipelines: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1223,7 +667,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -1233,22 +676,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.ListPipelinesRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseListPipelines._get_unset_required_fields(query_params)) - - return query_params - class _BaseListProviders: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1259,7 +686,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -1269,22 +695,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.ListProvidersRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseListProviders._get_unset_required_fields(query_params)) - - return query_params - class _BaseListTriggers: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1295,7 +705,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -1305,22 +714,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.ListTriggersRequest.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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseListTriggers._get_unset_required_fields(query_params)) - - return query_params - class _BaseUpdateChannel: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1335,30 +728,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.UpdateChannelRequest.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=False - ) - 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=False, - )) - - return query_params - class _BaseUpdateEnrollment: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1369,7 +738,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -1380,31 +748,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.UpdateEnrollmentRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseUpdateEnrollment._get_unset_required_fields(query_params)) - - return query_params - class _BaseUpdateGoogleApiSource: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1415,7 +758,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -1426,31 +768,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.UpdateGoogleApiSourceRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseUpdateGoogleApiSource._get_unset_required_fields(query_params)) - - return query_params - class _BaseUpdateGoogleChannelConfig: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1461,7 +778,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -1472,31 +788,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.UpdateGoogleChannelConfigRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseUpdateGoogleChannelConfig._get_unset_required_fields(query_params)) - - return query_params - class _BaseUpdateMessageBus: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1507,7 +798,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -1518,31 +808,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.UpdateMessageBusRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseUpdateMessageBus._get_unset_required_fields(query_params)) - - return query_params - class _BaseUpdatePipeline: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1553,7 +818,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -1564,31 +828,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.UpdatePipelineRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseEventarcRestTransport._BaseUpdatePipeline._get_unset_required_fields(query_params)) - - return query_params - class _BaseUpdateTrigger: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1603,31 +842,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = eventarc.UpdateTriggerRequest.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=False - ) - 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=False, - )) - - return query_params - class _BaseGetLocation: + """Base class for GetLocation REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1640,19 +856,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseListLocations: + """Base class for ListLocations REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1665,19 +870,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseGetIamPolicy: + """Base class for GetIamPolicy REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1698,19 +892,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseSetIamPolicy: + """Base class for SetIamPolicy REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1734,23 +917,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_request_body_json(transcoded_request): - body = json.dumps(transcoded_request['body']) - return body - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseTestIamPermissions: + """Base class for TestIamPermissions REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1774,23 +942,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_request_body_json(transcoded_request): - body = json.dumps(transcoded_request['body']) - return body - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseCancelOperation: + """Base class for CancelOperation REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1804,23 +957,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_request_body_json(transcoded_request): - body = json.dumps(transcoded_request['body']) - return body - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseDeleteOperation: + """Base class for DeleteOperation REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1833,19 +971,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseGetOperation: + """Base class for GetOperation REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1858,19 +985,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseListOperations: + """Base class for ListOperations REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -1883,18 +999,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - __all__=( '_BaseEventarcRestTransport', diff --git a/packages/gapic-generator/tests/integration/goldens/eventarc/tests/unit/gapic/eventarc_v1/test_eventarc.py b/packages/gapic-generator/tests/integration/goldens/eventarc/tests/unit/gapic/eventarc_v1/test_eventarc.py index 533e401eb1e7..b9495ef6e42d 100755 --- a/packages/gapic-generator/tests/integration/goldens/eventarc/tests/unit/gapic/eventarc_v1/test_eventarc.py +++ b/packages/gapic-generator/tests/integration/goldens/eventarc/tests/unit/gapic/eventarc_v1/test_eventarc.py @@ -721,7 +721,7 @@ def test_eventarc_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -768,7 +768,7 @@ def test_eventarc_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py new file mode 100644 index 000000000000..3bb07e7d2b4b --- /dev/null +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py @@ -0,0 +1,315 @@ +# # Copyright 2026 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. +# + +"""A compatibility module for older versions of google-api-core.""" + +import functools +import json +import operator +import os +import re +import uuid +from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from google.auth.exceptions import MutualTLSChannelError +import google.protobuf.message + + +try: + from google.api_core.universe import ( + get_default_mtls_endpoint, + get_api_endpoint, + get_universe_domain, + ) +except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]: + """Converts api endpoint to mTLS 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) + if m is None: + # Could not parse api_endpoint; return as-is. + return 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") + + def get_api_endpoint( + api_override: Optional[str], + client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + universe_domain: str, + use_mtls_endpoint: str, + default_universe: str, + default_mtls_endpoint: Optional[str], + default_endpoint_template: str, + ) -> Optional[str]: + """Return the API endpoint 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): + if universe_domain != default_universe: + raise MutualTLSChannelError( + f"mTLS is not supported in any universe other than {default_universe}." + ) + api_endpoint = default_mtls_endpoint + else: + api_endpoint = default_endpoint_template.format(UNIVERSE_DOMAIN=universe_domain) + return api_endpoint + + def get_universe_domain( + client_universe_domain: Optional[str], + universe_domain_env: Optional[str], + default_universe: str, + ) -> str: + """Return the universe domain used by the client.""" + universe_domain = 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 + + +try: + from google.api_core.gapic_v1.config import ( + use_client_cert_effective, + get_client_cert_source, + read_environment_variables, + ) +except ImportError: + from google.auth.transport import mtls # type: ignore + + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + + def use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + if hasattr(mtls, "should_use_client_cert"): + return mtls.should_use_client_cert() + else: + use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() + if use_client_cert_str not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" + " either `true` or `false`" + ) + return use_client_cert_str == "true" + + def get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return 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 ( + hasattr(mtls, "has_default_client_cert_source") + and mtls.has_default_client_cert_source() + ): + client_cert_source = mtls.default_client_cert_source() + else: + raise ValueError( + "Client certificate is required for mTLS, but no client certificate source was provided or found." + ) + return client_cert_source + + def read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + use_client_cert = use_client_cert_effective() + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() + universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") + 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, use_mtls_endpoint, universe_domain_env + + +try: + from google.api_core.gapic_v1.request import setup_request_id # type: ignore +except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. + def setup_request_id(request, field_name: str, is_proto3_optional: bool): + """Populate a UUID4 field in the request if it is not already set. + + Args: + request (Union[google.protobuf.message.Message, dict]): The request object. + field_name (str): The name of the field to populate. + is_proto3_optional (bool): Whether the field is proto3 optional. + """ + request_id_val = str(uuid.uuid4()) + if request is None: + return + + if isinstance(request, dict): + if is_proto3_optional: + if field_name not in request or request[field_name] is None: + request[field_name] = request_id_val + elif not request.get(field_name): + request[field_name] = request_id_val + return + + if is_proto3_optional: + try: + # Pure protobuf messages + if not request.HasField(field_name): + setattr(request, field_name, request_id_val) + except (AttributeError, ValueError): + # Proto-plus messages or other objects + if getattr(request, field_name, None) is None: + setattr(request, field_name, request_id_val) + else: + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) + + +try: + from google.api_core.rest_helpers import ( # type: ignore + flatten_query_params, + transcode_request as _core_transcode_request, + ) + import inspect + if "rest_numeric_enums" not in inspect.signature(_core_transcode_request).parameters: + raise ImportError + transcode_request = _core_transcode_request +except (ImportError, AttributeError): # pragma: NO COVER + # TODO: Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + from google.protobuf import json_format # type: ignore + from google.api_core import path_template # type: ignore + + def flatten_query_params(obj, strict=False): # pragma: NO COVER + if obj is not None and not isinstance(obj, dict): + raise TypeError("flatten_query_params must be called with dict object") + return _flatten(obj, key_path=[], strict=strict) + + def _flatten(obj, key_path, strict=False): # pragma: NO COVER + if obj is None: + return [] + if isinstance(obj, dict): + return _flatten_dict(obj, key_path=key_path, strict=strict) + if isinstance(obj, list): + return _flatten_list(obj, key_path=key_path, strict=strict) + return _flatten_value(obj, key_path=key_path, strict=strict) + + def _is_primitive_value(obj): # pragma: NO COVER + if obj is None: + return False + if isinstance(obj, (list, dict)): + raise ValueError("query params may not contain repeated dicts or lists") + return True + + def _flatten_value(obj, key_path, strict=False): # pragma: NO COVER + return [(".".join(key_path), _canonicalize(obj, strict=strict))] + + def _flatten_dict(obj, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten(value, key_path=key_path + [key], strict=strict) + for key, value in obj.items() + ) + return functools.reduce(operator.concat, items, []) # type: ignore + + def _flatten_list(elems, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten_value(elem, key_path=key_path, strict=strict) + for elem in elems + if _is_primitive_value(elem) + ) + return functools.reduce(operator.concat, items, []) # type: ignore + + def _canonicalize(obj, strict=False): # pragma: NO COVER + if strict: + value = str(obj) + if isinstance(obj, bool): + value = value.lower() + return value + return obj + + def transcode_request( # pragma: NO COVER + http_options: List[Dict[str, str]], + request: Any, + required_fields_default_values: Optional[Dict[str, Any]] = None, + rest_numeric_enums: bool = False, + ) -> Tuple[Dict[str, Any], Optional[str], Dict[str, Any]]: + pb_request = getattr(request, "_pb", request) + transcoded_request = path_template.transcode(http_options, pb_request) + + body_json = None + if transcoded_request.get("body") is not None: + body_json = json_format.MessageToJson( + transcoded_request["body"], + use_integers_for_enums=rest_numeric_enums, + ) + + query_params_json = {} + if transcoded_request.get("query_params") is not None: + query_params_json = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + use_integers_for_enums=rest_numeric_enums, + ) + ) + + if required_fields_default_values: + matched_option = None + for option in http_options: + if option.get("method", "").lower() == transcoded_request.get("method", "").lower(): + if path_template.validate( + option.get("uri", ""), transcoded_request.get("uri", "") + ): + matched_option = option + break + + bound_fields = set() + if matched_option: + uri_template = matched_option.get("uri", "") + for m in path_template._VARIABLE_RE.finditer(uri_template): + name = m.group("name") + if name: + bound_fields.add(name.split(".")[0]) + body_param = matched_option.get("body") + if body_param: + if body_param == "*": + bound_fields = None + else: + bound_fields.add(body_param.split(".")[0]) + + if bound_fields is not None: + for k, v in required_fields_default_values.items(): + if k in bound_fields: + continue + if k not in query_params_json: + query_params_json[k] = v + + if rest_numeric_enums: + query_params_json["$alt"] = "json;enum-encoding=int" + + return transcoded_request, body_json, query_params_json diff --git a/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_config_service_v2.py b/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_config_service_v2.py index eada5b433c55..8cc17d810664 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_config_service_v2.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_config_service_v2.py @@ -678,7 +678,7 @@ def test_config_service_v2_client_get_mtls_endpoint_and_cert_source(client_class config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -725,7 +725,7 @@ def test_config_service_v2_client_get_mtls_endpoint_and_cert_source(client_class config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): diff --git a/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_logging_service_v2.py b/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_logging_service_v2.py index 65559a5d1073..fd56f0210d78 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_logging_service_v2.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_logging_service_v2.py @@ -679,7 +679,7 @@ def test_logging_service_v2_client_get_mtls_endpoint_and_cert_source(client_clas config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -726,7 +726,7 @@ def test_logging_service_v2_client_get_mtls_endpoint_and_cert_source(client_clas config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): diff --git a/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_metrics_service_v2.py b/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_metrics_service_v2.py index 90cdab2be2b2..762b4b3ab94d 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_metrics_service_v2.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_metrics_service_v2.py @@ -677,7 +677,7 @@ def test_metrics_service_v2_client_get_mtls_endpoint_and_cert_source(client_clas config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -724,7 +724,7 @@ def test_metrics_service_v2_client_get_mtls_endpoint_and_cert_source(client_clas config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py new file mode 100644 index 000000000000..6f17d9fcc3ea --- /dev/null +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py @@ -0,0 +1,314 @@ +# # Copyright 2026 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. +# +"""A compatibility module for older versions of google-api-core.""" + +import functools +import json +import operator +import os +import re +import uuid +from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from google.auth.exceptions import MutualTLSChannelError +import google.protobuf.message + + +try: + from google.api_core.universe import ( + get_default_mtls_endpoint, + get_api_endpoint, + get_universe_domain, + ) +except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]: + """Converts api endpoint to mTLS 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) + if m is None: + # Could not parse api_endpoint; return as-is. + return 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") + + def get_api_endpoint( + api_override: Optional[str], + client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + universe_domain: str, + use_mtls_endpoint: str, + default_universe: str, + default_mtls_endpoint: Optional[str], + default_endpoint_template: str, + ) -> Optional[str]: + """Return the API endpoint 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): + if universe_domain != default_universe: + raise MutualTLSChannelError( + f"mTLS is not supported in any universe other than {default_universe}." + ) + api_endpoint = default_mtls_endpoint + else: + api_endpoint = default_endpoint_template.format(UNIVERSE_DOMAIN=universe_domain) + return api_endpoint + + def get_universe_domain( + client_universe_domain: Optional[str], + universe_domain_env: Optional[str], + default_universe: str, + ) -> str: + """Return the universe domain used by the client.""" + universe_domain = 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 + + +try: + from google.api_core.gapic_v1.config import ( + use_client_cert_effective, + get_client_cert_source, + read_environment_variables, + ) +except ImportError: + from google.auth.transport import mtls # type: ignore + + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + + def use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + if hasattr(mtls, "should_use_client_cert"): + return mtls.should_use_client_cert() + else: + use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() + if use_client_cert_str not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" + " either `true` or `false`" + ) + return use_client_cert_str == "true" + + def get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return 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 ( + hasattr(mtls, "has_default_client_cert_source") + and mtls.has_default_client_cert_source() + ): + client_cert_source = mtls.default_client_cert_source() + else: + raise ValueError( + "Client certificate is required for mTLS, but no client certificate source was provided or found." + ) + return client_cert_source + + def read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + use_client_cert = use_client_cert_effective() + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() + universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") + 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, use_mtls_endpoint, universe_domain_env + + +try: + from google.api_core.gapic_v1.request import setup_request_id # type: ignore +except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. + def setup_request_id(request, field_name: str, is_proto3_optional: bool): + """Populate a UUID4 field in the request if it is not already set. + + Args: + request (Union[google.protobuf.message.Message, dict]): The request object. + field_name (str): The name of the field to populate. + is_proto3_optional (bool): Whether the field is proto3 optional. + """ + request_id_val = str(uuid.uuid4()) + if request is None: + return + + if isinstance(request, dict): + if is_proto3_optional: + if field_name not in request or request[field_name] is None: + request[field_name] = request_id_val + elif not request.get(field_name): + request[field_name] = request_id_val + return + + if is_proto3_optional: + try: + # Pure protobuf messages + if not request.HasField(field_name): + setattr(request, field_name, request_id_val) + except (AttributeError, ValueError): + # Proto-plus messages or other objects + if getattr(request, field_name, None) is None: + setattr(request, field_name, request_id_val) + else: + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) + + +try: + from google.api_core.rest_helpers import ( # type: ignore + flatten_query_params, + transcode_request as _core_transcode_request, + ) + import inspect + if "rest_numeric_enums" not in inspect.signature(_core_transcode_request).parameters: + raise ImportError + transcode_request = _core_transcode_request +except (ImportError, AttributeError): # pragma: NO COVER + # TODO: Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + from google.protobuf import json_format # type: ignore + from google.api_core import path_template # type: ignore + + def flatten_query_params(obj, strict=False): # pragma: NO COVER + if obj is not None and not isinstance(obj, dict): + raise TypeError("flatten_query_params must be called with dict object") + return _flatten(obj, key_path=[], strict=strict) + + def _flatten(obj, key_path, strict=False): # pragma: NO COVER + if obj is None: + return [] + if isinstance(obj, dict): + return _flatten_dict(obj, key_path=key_path, strict=strict) + if isinstance(obj, list): + return _flatten_list(obj, key_path=key_path, strict=strict) + return _flatten_value(obj, key_path=key_path, strict=strict) + + def _is_primitive_value(obj): # pragma: NO COVER + if obj is None: + return False + if isinstance(obj, (list, dict)): + raise ValueError("query params may not contain repeated dicts or lists") + return True + + def _flatten_value(obj, key_path, strict=False): # pragma: NO COVER + return [(".".join(key_path), _canonicalize(obj, strict=strict))] + + def _flatten_dict(obj, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten(value, key_path=key_path + [key], strict=strict) + for key, value in obj.items() + ) + return functools.reduce(operator.concat, items, []) # type: ignore + + def _flatten_list(elems, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten_value(elem, key_path=key_path, strict=strict) + for elem in elems + if _is_primitive_value(elem) + ) + return functools.reduce(operator.concat, items, []) # type: ignore + + def _canonicalize(obj, strict=False): # pragma: NO COVER + if strict: + value = str(obj) + if isinstance(obj, bool): + value = value.lower() + return value + return obj + + def transcode_request( # pragma: NO COVER + http_options: List[Dict[str, str]], + request: Any, + required_fields_default_values: Optional[Dict[str, Any]] = None, + rest_numeric_enums: bool = False, + ) -> Tuple[Dict[str, Any], Optional[str], Dict[str, Any]]: + pb_request = getattr(request, "_pb", request) + transcoded_request = path_template.transcode(http_options, pb_request) + + body_json = None + if transcoded_request.get("body") is not None: + body_json = json_format.MessageToJson( + transcoded_request["body"], + use_integers_for_enums=rest_numeric_enums, + ) + + query_params_json = {} + if transcoded_request.get("query_params") is not None: + query_params_json = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + use_integers_for_enums=rest_numeric_enums, + ) + ) + + if required_fields_default_values: + matched_option = None + for option in http_options: + if option.get("method", "").lower() == transcoded_request.get("method", "").lower(): + if path_template.validate( + option.get("uri", ""), transcoded_request.get("uri", "") + ): + matched_option = option + break + + bound_fields = set() + if matched_option: + uri_template = matched_option.get("uri", "") + for m in path_template._VARIABLE_RE.finditer(uri_template): + name = m.group("name") + if name: + bound_fields.add(name.split(".")[0]) + body_param = matched_option.get("body") + if body_param: + if body_param == "*": + bound_fields = None + else: + bound_fields.add(body_param.split(".")[0]) + + if bound_fields is not None: + for k, v in required_fields_default_values.items(): + if k in bound_fields: + continue + if k not in query_params_json: + query_params_json[k] = v + + if rest_numeric_enums: + query_params_json["$alt"] = "json;enum-encoding=int" + + return transcoded_request, body_json, query_params_json diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_config_service_v2.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_config_service_v2.py index 9eec837e6f58..93731051d1bc 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_config_service_v2.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_config_service_v2.py @@ -678,7 +678,7 @@ def test_base_config_service_v2_client_get_mtls_endpoint_and_cert_source(client_ config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -725,7 +725,7 @@ def test_base_config_service_v2_client_get_mtls_endpoint_and_cert_source(client_ config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_logging_service_v2.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_logging_service_v2.py index 65559a5d1073..fd56f0210d78 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_logging_service_v2.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_logging_service_v2.py @@ -679,7 +679,7 @@ def test_logging_service_v2_client_get_mtls_endpoint_and_cert_source(client_clas config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -726,7 +726,7 @@ def test_logging_service_v2_client_get_mtls_endpoint_and_cert_source(client_clas config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_metrics_service_v2.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_metrics_service_v2.py index 310677b64bc6..090ea4a91bec 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_metrics_service_v2.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_metrics_service_v2.py @@ -677,7 +677,7 @@ def test_base_metrics_service_v2_client_get_mtls_endpoint_and_cert_source(client config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -724,7 +724,7 @@ def test_base_metrics_service_v2_client_get_mtls_endpoint_and_cert_source(client config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): diff --git a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py new file mode 100644 index 000000000000..6f17d9fcc3ea --- /dev/null +++ b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py @@ -0,0 +1,314 @@ +# # Copyright 2026 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. +# +"""A compatibility module for older versions of google-api-core.""" + +import functools +import json +import operator +import os +import re +import uuid +from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from google.auth.exceptions import MutualTLSChannelError +import google.protobuf.message + + +try: + from google.api_core.universe import ( + get_default_mtls_endpoint, + get_api_endpoint, + get_universe_domain, + ) +except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]: + """Converts api endpoint to mTLS 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) + if m is None: + # Could not parse api_endpoint; return as-is. + return 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") + + def get_api_endpoint( + api_override: Optional[str], + client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + universe_domain: str, + use_mtls_endpoint: str, + default_universe: str, + default_mtls_endpoint: Optional[str], + default_endpoint_template: str, + ) -> Optional[str]: + """Return the API endpoint 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): + if universe_domain != default_universe: + raise MutualTLSChannelError( + f"mTLS is not supported in any universe other than {default_universe}." + ) + api_endpoint = default_mtls_endpoint + else: + api_endpoint = default_endpoint_template.format(UNIVERSE_DOMAIN=universe_domain) + return api_endpoint + + def get_universe_domain( + client_universe_domain: Optional[str], + universe_domain_env: Optional[str], + default_universe: str, + ) -> str: + """Return the universe domain used by the client.""" + universe_domain = 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 + + +try: + from google.api_core.gapic_v1.config import ( + use_client_cert_effective, + get_client_cert_source, + read_environment_variables, + ) +except ImportError: + from google.auth.transport import mtls # type: ignore + + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + + def use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + if hasattr(mtls, "should_use_client_cert"): + return mtls.should_use_client_cert() + else: + use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() + if use_client_cert_str not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" + " either `true` or `false`" + ) + return use_client_cert_str == "true" + + def get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return 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 ( + hasattr(mtls, "has_default_client_cert_source") + and mtls.has_default_client_cert_source() + ): + client_cert_source = mtls.default_client_cert_source() + else: + raise ValueError( + "Client certificate is required for mTLS, but no client certificate source was provided or found." + ) + return client_cert_source + + def read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + use_client_cert = use_client_cert_effective() + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() + universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") + 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, use_mtls_endpoint, universe_domain_env + + +try: + from google.api_core.gapic_v1.request import setup_request_id # type: ignore +except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. + def setup_request_id(request, field_name: str, is_proto3_optional: bool): + """Populate a UUID4 field in the request if it is not already set. + + Args: + request (Union[google.protobuf.message.Message, dict]): The request object. + field_name (str): The name of the field to populate. + is_proto3_optional (bool): Whether the field is proto3 optional. + """ + request_id_val = str(uuid.uuid4()) + if request is None: + return + + if isinstance(request, dict): + if is_proto3_optional: + if field_name not in request or request[field_name] is None: + request[field_name] = request_id_val + elif not request.get(field_name): + request[field_name] = request_id_val + return + + if is_proto3_optional: + try: + # Pure protobuf messages + if not request.HasField(field_name): + setattr(request, field_name, request_id_val) + except (AttributeError, ValueError): + # Proto-plus messages or other objects + if getattr(request, field_name, None) is None: + setattr(request, field_name, request_id_val) + else: + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) + + +try: + from google.api_core.rest_helpers import ( # type: ignore + flatten_query_params, + transcode_request as _core_transcode_request, + ) + import inspect + if "rest_numeric_enums" not in inspect.signature(_core_transcode_request).parameters: + raise ImportError + transcode_request = _core_transcode_request +except (ImportError, AttributeError): # pragma: NO COVER + # TODO: Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + from google.protobuf import json_format # type: ignore + from google.api_core import path_template # type: ignore + + def flatten_query_params(obj, strict=False): # pragma: NO COVER + if obj is not None and not isinstance(obj, dict): + raise TypeError("flatten_query_params must be called with dict object") + return _flatten(obj, key_path=[], strict=strict) + + def _flatten(obj, key_path, strict=False): # pragma: NO COVER + if obj is None: + return [] + if isinstance(obj, dict): + return _flatten_dict(obj, key_path=key_path, strict=strict) + if isinstance(obj, list): + return _flatten_list(obj, key_path=key_path, strict=strict) + return _flatten_value(obj, key_path=key_path, strict=strict) + + def _is_primitive_value(obj): # pragma: NO COVER + if obj is None: + return False + if isinstance(obj, (list, dict)): + raise ValueError("query params may not contain repeated dicts or lists") + return True + + def _flatten_value(obj, key_path, strict=False): # pragma: NO COVER + return [(".".join(key_path), _canonicalize(obj, strict=strict))] + + def _flatten_dict(obj, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten(value, key_path=key_path + [key], strict=strict) + for key, value in obj.items() + ) + return functools.reduce(operator.concat, items, []) # type: ignore + + def _flatten_list(elems, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten_value(elem, key_path=key_path, strict=strict) + for elem in elems + if _is_primitive_value(elem) + ) + return functools.reduce(operator.concat, items, []) # type: ignore + + def _canonicalize(obj, strict=False): # pragma: NO COVER + if strict: + value = str(obj) + if isinstance(obj, bool): + value = value.lower() + return value + return obj + + def transcode_request( # pragma: NO COVER + http_options: List[Dict[str, str]], + request: Any, + required_fields_default_values: Optional[Dict[str, Any]] = None, + rest_numeric_enums: bool = False, + ) -> Tuple[Dict[str, Any], Optional[str], Dict[str, Any]]: + pb_request = getattr(request, "_pb", request) + transcoded_request = path_template.transcode(http_options, pb_request) + + body_json = None + if transcoded_request.get("body") is not None: + body_json = json_format.MessageToJson( + transcoded_request["body"], + use_integers_for_enums=rest_numeric_enums, + ) + + query_params_json = {} + if transcoded_request.get("query_params") is not None: + query_params_json = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + use_integers_for_enums=rest_numeric_enums, + ) + ) + + if required_fields_default_values: + matched_option = None + for option in http_options: + if option.get("method", "").lower() == transcoded_request.get("method", "").lower(): + if path_template.validate( + option.get("uri", ""), transcoded_request.get("uri", "") + ): + matched_option = option + break + + bound_fields = set() + if matched_option: + uri_template = matched_option.get("uri", "") + for m in path_template._VARIABLE_RE.finditer(uri_template): + name = m.group("name") + if name: + bound_fields.add(name.split(".")[0]) + body_param = matched_option.get("body") + if body_param: + if body_param == "*": + bound_fields = None + else: + bound_fields.add(body_param.split(".")[0]) + + if bound_fields is not None: + for k, v in required_fields_default_values.items(): + if k in bound_fields: + continue + if k not in query_params_json: + query_params_json[k] = v + + if rest_numeric_enums: + query_params_json["$alt"] = "json;enum-encoding=int" + + return transcoded_request, body_json, query_params_json diff --git a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest.py b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest.py index 013062f304b2..8b3ff81f649f 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest.py +++ b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest.py @@ -20,7 +20,7 @@ from google.auth import credentials as ga_credentials # type: ignore from google.api_core import exceptions as core_exceptions from google.api_core import retry as retries -from google.api_core import rest_helpers +from google.cloud.redis_v1 import _compat as rest_helpers from google.api_core import rest_streaming from google.api_core import gapic_v1 import google.protobuf @@ -944,14 +944,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseCreateInstance._get_http_options() - request, metadata = self._interceptor.pre_create_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseCreateInstance._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseCreateInstance._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseCreateInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseCreateInstance, + "_BaseCreateInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1067,12 +1070,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseDeleteInstance._get_http_options() - request, metadata = self._interceptor.pre_delete_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseDeleteInstance._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseDeleteInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseDeleteInstance, + "_BaseDeleteInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1189,14 +1197,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseExportInstance._get_http_options() - request, metadata = self._interceptor.pre_export_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseExportInstance._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseExportInstance._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseExportInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseExportInstance, + "_BaseExportInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1313,14 +1324,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseFailoverInstance._get_http_options() - request, metadata = self._interceptor.pre_failover_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseFailoverInstance._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseFailoverInstance._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseFailoverInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseFailoverInstance, + "_BaseFailoverInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1433,12 +1447,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseGetInstance._get_http_options() - request, metadata = self._interceptor.pre_get_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseGetInstance._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseGetInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseGetInstance, + "_BaseGetInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1553,12 +1572,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseGetInstanceAuthString._get_http_options() - request, metadata = self._interceptor.pre_get_instance_auth_string(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseGetInstanceAuthString._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseGetInstanceAuthString._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseGetInstanceAuthString, + "_BaseGetInstanceAuthString__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1677,14 +1701,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseImportInstance._get_http_options() - request, metadata = self._interceptor.pre_import_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseImportInstance._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseImportInstance._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseImportInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseImportInstance, + "_BaseImportInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1799,12 +1826,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseListInstances._get_http_options() - request, metadata = self._interceptor.pre_list_instances(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseListInstances._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseListInstances._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseListInstances, + "_BaseListInstances__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1923,14 +1955,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseRescheduleMaintenance._get_http_options() - request, metadata = self._interceptor.pre_reschedule_maintenance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseRescheduleMaintenance._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseRescheduleMaintenance._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseRescheduleMaintenance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseRescheduleMaintenance, + "_BaseRescheduleMaintenance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2047,14 +2082,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseUpdateInstance._get_http_options() - request, metadata = self._interceptor.pre_update_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseUpdateInstance._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseUpdateInstance._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseUpdateInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseUpdateInstance, + "_BaseUpdateInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2171,14 +2209,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseUpgradeInstance._get_http_options() - request, metadata = self._interceptor.pre_upgrade_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseUpgradeInstance._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseUpgradeInstance._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseUpgradeInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseUpgradeInstance, + "_BaseUpgradeInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2382,12 +2423,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseGetLocation._get_http_options() - request, metadata = self._interceptor.pre_get_location(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseGetLocation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseGetLocation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseGetLocation, + "_BaseGetLocation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2500,12 +2546,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseListLocations._get_http_options() - request, metadata = self._interceptor.pre_list_locations(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseListLocations._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseListLocations._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseListLocations, + "_BaseListLocations__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2615,12 +2666,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseCancelOperation._get_http_options() - request, metadata = self._interceptor.pre_cancel_operation(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseCancelOperation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseCancelOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseCancelOperation, + "_BaseCancelOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2707,12 +2763,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseDeleteOperation._get_http_options() - request, metadata = self._interceptor.pre_delete_operation(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseDeleteOperation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseDeleteOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseDeleteOperation, + "_BaseDeleteOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2802,12 +2863,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseGetOperation._get_http_options() - request, metadata = self._interceptor.pre_get_operation(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseGetOperation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseGetOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseGetOperation, + "_BaseGetOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2920,12 +2986,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseListOperations._get_http_options() - request, metadata = self._interceptor.pre_list_operations(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseListOperations._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseListOperations._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseListOperations, + "_BaseListOperations__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -3039,14 +3110,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseWaitOperation._get_http_options() - request, metadata = self._interceptor.pre_wait_operation(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseWaitOperation._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseWaitOperation._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseWaitOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseWaitOperation, + "_BaseWaitOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) diff --git a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest_asyncio.py b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest_asyncio.py index d827de47ee24..5867f95994b6 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest_asyncio.py +++ b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest_asyncio.py @@ -30,7 +30,7 @@ from google.api_core import operations_v1 from google.cloud.location import locations_pb2 # type: ignore from google.api_core import retry_async as retries -from google.api_core import rest_helpers +from google.cloud.redis_v1 import _compat as rest_helpers from google.api_core import rest_streaming_async # type: ignore import google.protobuf @@ -969,14 +969,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseCreateInstance._get_http_options() - request, metadata = await self._interceptor.pre_create_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseCreateInstance._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseCreateInstance._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseCreateInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseCreateInstance, + "_BaseCreateInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1098,12 +1101,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseDeleteInstance._get_http_options() - request, metadata = await self._interceptor.pre_delete_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseDeleteInstance._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseDeleteInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseDeleteInstance, + "_BaseDeleteInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1226,14 +1234,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseExportInstance._get_http_options() - request, metadata = await self._interceptor.pre_export_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseExportInstance._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseExportInstance._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseExportInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseExportInstance, + "_BaseExportInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1356,14 +1367,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseFailoverInstance._get_http_options() - request, metadata = await self._interceptor.pre_failover_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseFailoverInstance._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseFailoverInstance._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseFailoverInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseFailoverInstance, + "_BaseFailoverInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1482,12 +1496,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseGetInstance._get_http_options() - request, metadata = await self._interceptor.pre_get_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseGetInstance._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseGetInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseGetInstance, + "_BaseGetInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1606,12 +1625,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseGetInstanceAuthString._get_http_options() - request, metadata = await self._interceptor.pre_get_instance_auth_string(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseGetInstanceAuthString._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseGetInstanceAuthString._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseGetInstanceAuthString, + "_BaseGetInstanceAuthString__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1734,14 +1758,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseImportInstance._get_http_options() - request, metadata = await self._interceptor.pre_import_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseImportInstance._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseImportInstance._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseImportInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseImportInstance, + "_BaseImportInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1862,12 +1889,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseListInstances._get_http_options() - request, metadata = await self._interceptor.pre_list_instances(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseListInstances._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseListInstances._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseListInstances, + "_BaseListInstances__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1990,14 +2022,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseRescheduleMaintenance._get_http_options() - request, metadata = await self._interceptor.pre_reschedule_maintenance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseRescheduleMaintenance._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseRescheduleMaintenance._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseRescheduleMaintenance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseRescheduleMaintenance, + "_BaseRescheduleMaintenance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2120,14 +2155,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseUpdateInstance._get_http_options() - request, metadata = await self._interceptor.pre_update_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseUpdateInstance._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseUpdateInstance._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseUpdateInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseUpdateInstance, + "_BaseUpdateInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2250,14 +2288,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseUpgradeInstance._get_http_options() - request, metadata = await self._interceptor.pre_upgrade_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseUpgradeInstance._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseUpgradeInstance._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseUpgradeInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseUpgradeInstance, + "_BaseUpgradeInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2501,12 +2542,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseGetLocation._get_http_options() - request, metadata = await self._interceptor.pre_get_location(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseGetLocation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseGetLocation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseGetLocation, + "_BaseGetLocation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2623,12 +2669,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseListLocations._get_http_options() - request, metadata = await self._interceptor.pre_list_locations(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseListLocations._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseListLocations._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseListLocations, + "_BaseListLocations__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2742,12 +2793,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseCancelOperation._get_http_options() - request, metadata = await self._interceptor.pre_cancel_operation(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseCancelOperation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseCancelOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseCancelOperation, + "_BaseCancelOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2838,12 +2894,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseDeleteOperation._get_http_options() - request, metadata = await self._interceptor.pre_delete_operation(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseDeleteOperation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseDeleteOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseDeleteOperation, + "_BaseDeleteOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2937,12 +2998,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseGetOperation._get_http_options() - request, metadata = await self._interceptor.pre_get_operation(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseGetOperation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseGetOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseGetOperation, + "_BaseGetOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -3059,12 +3125,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseListOperations._get_http_options() - request, metadata = await self._interceptor.pre_list_operations(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseListOperations._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseListOperations._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseListOperations, + "_BaseListOperations__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -3182,14 +3253,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseWaitOperation._get_http_options() - request, metadata = await self._interceptor.pre_wait_operation(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseWaitOperation._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseWaitOperation._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseWaitOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseWaitOperation, + "_BaseWaitOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) diff --git a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest_base.py b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest_base.py index 65352deb5bbf..48fb3713325a 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest_base.py +++ b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest_base.py @@ -97,7 +97,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -108,31 +107,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = cloud_redis.CreateInstanceRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseCloudRedisRestTransport._BaseCreateInstance._get_unset_required_fields(query_params)) - - return query_params - class _BaseDeleteInstance: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -143,7 +117,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -153,22 +126,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = cloud_redis.DeleteInstanceRequest.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=False, - )) - query_params.update(_BaseCloudRedisRestTransport._BaseDeleteInstance._get_unset_required_fields(query_params)) - - return query_params - class _BaseExportInstance: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -179,7 +136,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -190,31 +146,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = cloud_redis.ExportInstanceRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseCloudRedisRestTransport._BaseExportInstance._get_unset_required_fields(query_params)) - - return query_params - class _BaseFailoverInstance: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -225,7 +156,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -236,31 +166,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = cloud_redis.FailoverInstanceRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseCloudRedisRestTransport._BaseFailoverInstance._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetInstance: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -271,7 +176,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -281,22 +185,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = cloud_redis.GetInstanceRequest.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=False, - )) - query_params.update(_BaseCloudRedisRestTransport._BaseGetInstance._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetInstanceAuthString: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -307,7 +195,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -317,22 +204,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = cloud_redis.GetInstanceAuthStringRequest.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=False, - )) - query_params.update(_BaseCloudRedisRestTransport._BaseGetInstanceAuthString._get_unset_required_fields(query_params)) - - return query_params - class _BaseImportInstance: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -343,7 +214,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -354,31 +224,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = cloud_redis.ImportInstanceRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseCloudRedisRestTransport._BaseImportInstance._get_unset_required_fields(query_params)) - - return query_params - class _BaseListInstances: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -389,7 +234,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -399,22 +243,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = cloud_redis.ListInstancesRequest.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=False, - )) - query_params.update(_BaseCloudRedisRestTransport._BaseListInstances._get_unset_required_fields(query_params)) - - return query_params - class _BaseRescheduleMaintenance: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -425,7 +253,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -436,31 +263,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = cloud_redis.RescheduleMaintenanceRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseCloudRedisRestTransport._BaseRescheduleMaintenance._get_unset_required_fields(query_params)) - - return query_params - class _BaseUpdateInstance: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -471,7 +273,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -482,31 +283,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = cloud_redis.UpdateInstanceRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseCloudRedisRestTransport._BaseUpdateInstance._get_unset_required_fields(query_params)) - - return query_params - class _BaseUpgradeInstance: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -517,7 +293,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -528,32 +303,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = cloud_redis.UpgradeInstanceRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseCloudRedisRestTransport._BaseUpgradeInstance._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetLocation: + """Base class for GetLocation REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -566,19 +317,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseListLocations: + """Base class for ListLocations REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -591,19 +331,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseCancelOperation: + """Base class for CancelOperation REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -616,19 +345,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseDeleteOperation: + """Base class for DeleteOperation REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -641,19 +359,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseGetOperation: + """Base class for GetOperation REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -666,19 +373,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseListOperations: + """Base class for ListOperations REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -691,19 +387,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseWaitOperation: + """Base class for WaitOperation REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -717,22 +402,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_request_body_json(transcoded_request): - body = json.dumps(transcoded_request['body']) - return body - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - __all__=( '_BaseCloudRedisRestTransport', diff --git a/packages/gapic-generator/tests/integration/goldens/redis/tests/unit/gapic/redis_v1/test_cloud_redis.py b/packages/gapic-generator/tests/integration/goldens/redis/tests/unit/gapic/redis_v1/test_cloud_redis.py index 8ca1fb5194a6..d58d99d1fdb8 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis/tests/unit/gapic/redis_v1/test_cloud_redis.py +++ b/packages/gapic-generator/tests/integration/goldens/redis/tests/unit/gapic/redis_v1/test_cloud_redis.py @@ -708,7 +708,7 @@ def test_cloud_redis_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -755,7 +755,7 @@ def test_cloud_redis_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): diff --git a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py new file mode 100644 index 000000000000..6f17d9fcc3ea --- /dev/null +++ b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py @@ -0,0 +1,314 @@ +# # Copyright 2026 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. +# +"""A compatibility module for older versions of google-api-core.""" + +import functools +import json +import operator +import os +import re +import uuid +from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from google.auth.exceptions import MutualTLSChannelError +import google.protobuf.message + + +try: + from google.api_core.universe import ( + get_default_mtls_endpoint, + get_api_endpoint, + get_universe_domain, + ) +except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]: + """Converts api endpoint to mTLS 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) + if m is None: + # Could not parse api_endpoint; return as-is. + return 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") + + def get_api_endpoint( + api_override: Optional[str], + client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + universe_domain: str, + use_mtls_endpoint: str, + default_universe: str, + default_mtls_endpoint: Optional[str], + default_endpoint_template: str, + ) -> Optional[str]: + """Return the API endpoint 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): + if universe_domain != default_universe: + raise MutualTLSChannelError( + f"mTLS is not supported in any universe other than {default_universe}." + ) + api_endpoint = default_mtls_endpoint + else: + api_endpoint = default_endpoint_template.format(UNIVERSE_DOMAIN=universe_domain) + return api_endpoint + + def get_universe_domain( + client_universe_domain: Optional[str], + universe_domain_env: Optional[str], + default_universe: str, + ) -> str: + """Return the universe domain used by the client.""" + universe_domain = 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 + + +try: + from google.api_core.gapic_v1.config import ( + use_client_cert_effective, + get_client_cert_source, + read_environment_variables, + ) +except ImportError: + from google.auth.transport import mtls # type: ignore + + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + + def use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + if hasattr(mtls, "should_use_client_cert"): + return mtls.should_use_client_cert() + else: + use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() + if use_client_cert_str not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" + " either `true` or `false`" + ) + return use_client_cert_str == "true" + + def get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return 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 ( + hasattr(mtls, "has_default_client_cert_source") + and mtls.has_default_client_cert_source() + ): + client_cert_source = mtls.default_client_cert_source() + else: + raise ValueError( + "Client certificate is required for mTLS, but no client certificate source was provided or found." + ) + return client_cert_source + + def read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + use_client_cert = use_client_cert_effective() + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() + universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") + 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, use_mtls_endpoint, universe_domain_env + + +try: + from google.api_core.gapic_v1.request import setup_request_id # type: ignore +except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. + def setup_request_id(request, field_name: str, is_proto3_optional: bool): + """Populate a UUID4 field in the request if it is not already set. + + Args: + request (Union[google.protobuf.message.Message, dict]): The request object. + field_name (str): The name of the field to populate. + is_proto3_optional (bool): Whether the field is proto3 optional. + """ + request_id_val = str(uuid.uuid4()) + if request is None: + return + + if isinstance(request, dict): + if is_proto3_optional: + if field_name not in request or request[field_name] is None: + request[field_name] = request_id_val + elif not request.get(field_name): + request[field_name] = request_id_val + return + + if is_proto3_optional: + try: + # Pure protobuf messages + if not request.HasField(field_name): + setattr(request, field_name, request_id_val) + except (AttributeError, ValueError): + # Proto-plus messages or other objects + if getattr(request, field_name, None) is None: + setattr(request, field_name, request_id_val) + else: + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) + + +try: + from google.api_core.rest_helpers import ( # type: ignore + flatten_query_params, + transcode_request as _core_transcode_request, + ) + import inspect + if "rest_numeric_enums" not in inspect.signature(_core_transcode_request).parameters: + raise ImportError + transcode_request = _core_transcode_request +except (ImportError, AttributeError): # pragma: NO COVER + # TODO: Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + from google.protobuf import json_format # type: ignore + from google.api_core import path_template # type: ignore + + def flatten_query_params(obj, strict=False): # pragma: NO COVER + if obj is not None and not isinstance(obj, dict): + raise TypeError("flatten_query_params must be called with dict object") + return _flatten(obj, key_path=[], strict=strict) + + def _flatten(obj, key_path, strict=False): # pragma: NO COVER + if obj is None: + return [] + if isinstance(obj, dict): + return _flatten_dict(obj, key_path=key_path, strict=strict) + if isinstance(obj, list): + return _flatten_list(obj, key_path=key_path, strict=strict) + return _flatten_value(obj, key_path=key_path, strict=strict) + + def _is_primitive_value(obj): # pragma: NO COVER + if obj is None: + return False + if isinstance(obj, (list, dict)): + raise ValueError("query params may not contain repeated dicts or lists") + return True + + def _flatten_value(obj, key_path, strict=False): # pragma: NO COVER + return [(".".join(key_path), _canonicalize(obj, strict=strict))] + + def _flatten_dict(obj, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten(value, key_path=key_path + [key], strict=strict) + for key, value in obj.items() + ) + return functools.reduce(operator.concat, items, []) # type: ignore + + def _flatten_list(elems, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten_value(elem, key_path=key_path, strict=strict) + for elem in elems + if _is_primitive_value(elem) + ) + return functools.reduce(operator.concat, items, []) # type: ignore + + def _canonicalize(obj, strict=False): # pragma: NO COVER + if strict: + value = str(obj) + if isinstance(obj, bool): + value = value.lower() + return value + return obj + + def transcode_request( # pragma: NO COVER + http_options: List[Dict[str, str]], + request: Any, + required_fields_default_values: Optional[Dict[str, Any]] = None, + rest_numeric_enums: bool = False, + ) -> Tuple[Dict[str, Any], Optional[str], Dict[str, Any]]: + pb_request = getattr(request, "_pb", request) + transcoded_request = path_template.transcode(http_options, pb_request) + + body_json = None + if transcoded_request.get("body") is not None: + body_json = json_format.MessageToJson( + transcoded_request["body"], + use_integers_for_enums=rest_numeric_enums, + ) + + query_params_json = {} + if transcoded_request.get("query_params") is not None: + query_params_json = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + use_integers_for_enums=rest_numeric_enums, + ) + ) + + if required_fields_default_values: + matched_option = None + for option in http_options: + if option.get("method", "").lower() == transcoded_request.get("method", "").lower(): + if path_template.validate( + option.get("uri", ""), transcoded_request.get("uri", "") + ): + matched_option = option + break + + bound_fields = set() + if matched_option: + uri_template = matched_option.get("uri", "") + for m in path_template._VARIABLE_RE.finditer(uri_template): + name = m.group("name") + if name: + bound_fields.add(name.split(".")[0]) + body_param = matched_option.get("body") + if body_param: + if body_param == "*": + bound_fields = None + else: + bound_fields.add(body_param.split(".")[0]) + + if bound_fields is not None: + for k, v in required_fields_default_values.items(): + if k in bound_fields: + continue + if k not in query_params_json: + query_params_json[k] = v + + if rest_numeric_enums: + query_params_json["$alt"] = "json;enum-encoding=int" + + return transcoded_request, body_json, query_params_json diff --git a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/transports/rest.py b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/transports/rest.py index 230965c05d9c..c0f1aa7277fa 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/transports/rest.py +++ b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/transports/rest.py @@ -20,7 +20,7 @@ from google.auth import credentials as ga_credentials # type: ignore from google.api_core import exceptions as core_exceptions from google.api_core import retry as retries -from google.api_core import rest_helpers +from google.cloud.redis_v1 import _compat as rest_helpers from google.api_core import rest_streaming from google.api_core import gapic_v1 import google.protobuf @@ -680,14 +680,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseCreateInstance._get_http_options() - request, metadata = self._interceptor.pre_create_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseCreateInstance._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseCreateInstance._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseCreateInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseCreateInstance, + "_BaseCreateInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -803,12 +806,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseDeleteInstance._get_http_options() - request, metadata = self._interceptor.pre_delete_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseDeleteInstance._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseDeleteInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseDeleteInstance, + "_BaseDeleteInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -921,12 +929,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseGetInstance._get_http_options() - request, metadata = self._interceptor.pre_get_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseGetInstance._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseGetInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseGetInstance, + "_BaseGetInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1043,12 +1056,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseListInstances._get_http_options() - request, metadata = self._interceptor.pre_list_instances(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseListInstances._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseListInstances._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseListInstances, + "_BaseListInstances__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1167,14 +1185,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseUpdateInstance._get_http_options() - request, metadata = self._interceptor.pre_update_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseUpdateInstance._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseUpdateInstance._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseUpdateInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseUpdateInstance, + "_BaseUpdateInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1330,12 +1351,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseGetLocation._get_http_options() - request, metadata = self._interceptor.pre_get_location(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseGetLocation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseGetLocation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseGetLocation, + "_BaseGetLocation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1448,12 +1474,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseListLocations._get_http_options() - request, metadata = self._interceptor.pre_list_locations(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseListLocations._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseListLocations._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseListLocations, + "_BaseListLocations__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1563,12 +1594,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseCancelOperation._get_http_options() - request, metadata = self._interceptor.pre_cancel_operation(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseCancelOperation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseCancelOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseCancelOperation, + "_BaseCancelOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1655,12 +1691,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseDeleteOperation._get_http_options() - request, metadata = self._interceptor.pre_delete_operation(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseDeleteOperation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseDeleteOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseDeleteOperation, + "_BaseDeleteOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1750,12 +1791,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseGetOperation._get_http_options() - request, metadata = self._interceptor.pre_get_operation(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseGetOperation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseGetOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseGetOperation, + "_BaseGetOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1868,12 +1914,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseListOperations._get_http_options() - request, metadata = self._interceptor.pre_list_operations(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseListOperations._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseListOperations._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseListOperations, + "_BaseListOperations__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1987,14 +2038,17 @@ def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseWaitOperation._get_http_options() - request, metadata = self._interceptor.pre_wait_operation(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseWaitOperation._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseWaitOperation._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseWaitOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseWaitOperation, + "_BaseWaitOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) diff --git a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/transports/rest_asyncio.py b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/transports/rest_asyncio.py index bcd5f851f97f..af14341a415a 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/transports/rest_asyncio.py +++ b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/transports/rest_asyncio.py @@ -30,7 +30,7 @@ from google.api_core import operations_v1 from google.cloud.location import locations_pb2 # type: ignore from google.api_core import retry_async as retries -from google.api_core import rest_helpers +from google.cloud.redis_v1 import _compat as rest_helpers from google.api_core import rest_streaming_async # type: ignore import google.protobuf @@ -675,14 +675,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseCreateInstance._get_http_options() - request, metadata = await self._interceptor.pre_create_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseCreateInstance._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseCreateInstance._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseCreateInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseCreateInstance, + "_BaseCreateInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -804,12 +807,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseDeleteInstance._get_http_options() - request, metadata = await self._interceptor.pre_delete_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseDeleteInstance._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseDeleteInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseDeleteInstance, + "_BaseDeleteInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -928,12 +936,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseGetInstance._get_http_options() - request, metadata = await self._interceptor.pre_get_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseGetInstance._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseGetInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseGetInstance, + "_BaseGetInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1054,12 +1067,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseListInstances._get_http_options() - request, metadata = await self._interceptor.pre_list_instances(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseListInstances._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseListInstances._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseListInstances, + "_BaseListInstances__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1182,14 +1200,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseUpdateInstance._get_http_options() - request, metadata = await self._interceptor.pre_update_instance(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseUpdateInstance._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseUpdateInstance._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseUpdateInstance._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseUpdateInstance, + "_BaseUpdateInstance__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1397,12 +1418,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseGetLocation._get_http_options() - request, metadata = await self._interceptor.pre_get_location(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseGetLocation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseGetLocation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseGetLocation, + "_BaseGetLocation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1519,12 +1545,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseListLocations._get_http_options() - request, metadata = await self._interceptor.pre_list_locations(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseListLocations._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseListLocations._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseListLocations, + "_BaseListLocations__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1638,12 +1669,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseCancelOperation._get_http_options() - request, metadata = await self._interceptor.pre_cancel_operation(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseCancelOperation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseCancelOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseCancelOperation, + "_BaseCancelOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1734,12 +1770,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseDeleteOperation._get_http_options() - request, metadata = await self._interceptor.pre_delete_operation(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseDeleteOperation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseDeleteOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseDeleteOperation, + "_BaseDeleteOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1833,12 +1874,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseGetOperation._get_http_options() - request, metadata = await self._interceptor.pre_get_operation(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseGetOperation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseGetOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseGetOperation, + "_BaseGetOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1955,12 +2001,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseListOperations._get_http_options() - request, metadata = await self._interceptor.pre_list_operations(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseListOperations._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseListOperations._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseListOperations, + "_BaseListOperations__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2078,14 +2129,17 @@ async def __call__(self, """ http_options = _BaseCloudRedisRestTransport._BaseWaitOperation._get_http_options() - request, metadata = await self._interceptor.pre_wait_operation(request, metadata) - transcoded_request = _BaseCloudRedisRestTransport._BaseWaitOperation._get_transcoded_request(http_options, request) - - body = _BaseCloudRedisRestTransport._BaseWaitOperation._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseCloudRedisRestTransport._BaseWaitOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseCloudRedisRestTransport._BaseWaitOperation, + "_BaseWaitOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) diff --git a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/transports/rest_base.py b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/transports/rest_base.py index ff18d15f1290..e280cdcbb135 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/transports/rest_base.py +++ b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/transports/rest_base.py @@ -97,7 +97,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -108,31 +107,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = cloud_redis.CreateInstanceRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseCloudRedisRestTransport._BaseCreateInstance._get_unset_required_fields(query_params)) - - return query_params - class _BaseDeleteInstance: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -143,7 +117,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -153,22 +126,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = cloud_redis.DeleteInstanceRequest.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=False, - )) - query_params.update(_BaseCloudRedisRestTransport._BaseDeleteInstance._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetInstance: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -179,7 +136,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -189,22 +145,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = cloud_redis.GetInstanceRequest.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=False, - )) - query_params.update(_BaseCloudRedisRestTransport._BaseGetInstance._get_unset_required_fields(query_params)) - - return query_params - class _BaseListInstances: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -215,7 +155,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -225,22 +164,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = cloud_redis.ListInstancesRequest.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=False, - )) - query_params.update(_BaseCloudRedisRestTransport._BaseListInstances._get_unset_required_fields(query_params)) - - return query_params - class _BaseUpdateInstance: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -251,7 +174,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -262,32 +184,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = cloud_redis.UpdateInstanceRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseCloudRedisRestTransport._BaseUpdateInstance._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetLocation: + """Base class for GetLocation REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -300,19 +198,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseListLocations: + """Base class for ListLocations REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -325,19 +212,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseCancelOperation: + """Base class for CancelOperation REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -350,19 +226,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseDeleteOperation: + """Base class for DeleteOperation REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -375,19 +240,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseGetOperation: + """Base class for GetOperation REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -400,19 +254,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseListOperations: + """Base class for ListOperations REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -425,19 +268,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseWaitOperation: + """Base class for WaitOperation REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -451,22 +283,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_request_body_json(transcoded_request): - body = json.dumps(transcoded_request['body']) - return body - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - __all__=( '_BaseCloudRedisRestTransport', diff --git a/packages/gapic-generator/tests/integration/goldens/redis_selective/tests/unit/gapic/redis_v1/test_cloud_redis.py b/packages/gapic-generator/tests/integration/goldens/redis_selective/tests/unit/gapic/redis_v1/test_cloud_redis.py index 3f6b7aa521f3..780964608350 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis_selective/tests/unit/gapic/redis_v1/test_cloud_redis.py +++ b/packages/gapic-generator/tests/integration/goldens/redis_selective/tests/unit/gapic/redis_v1/test_cloud_redis.py @@ -708,7 +708,7 @@ def test_cloud_redis_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -755,7 +755,7 @@ def test_cloud_redis_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py new file mode 100644 index 000000000000..6f17d9fcc3ea --- /dev/null +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py @@ -0,0 +1,314 @@ +# # Copyright 2026 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. +# +"""A compatibility module for older versions of google-api-core.""" + +import functools +import json +import operator +import os +import re +import uuid +from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from google.auth.exceptions import MutualTLSChannelError +import google.protobuf.message + + +try: + from google.api_core.universe import ( + get_default_mtls_endpoint, + get_api_endpoint, + get_universe_domain, + ) +except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]: + """Converts api endpoint to mTLS 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) + if m is None: + # Could not parse api_endpoint; return as-is. + return 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") + + def get_api_endpoint( + api_override: Optional[str], + client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + universe_domain: str, + use_mtls_endpoint: str, + default_universe: str, + default_mtls_endpoint: Optional[str], + default_endpoint_template: str, + ) -> Optional[str]: + """Return the API endpoint 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): + if universe_domain != default_universe: + raise MutualTLSChannelError( + f"mTLS is not supported in any universe other than {default_universe}." + ) + api_endpoint = default_mtls_endpoint + else: + api_endpoint = default_endpoint_template.format(UNIVERSE_DOMAIN=universe_domain) + return api_endpoint + + def get_universe_domain( + client_universe_domain: Optional[str], + universe_domain_env: Optional[str], + default_universe: str, + ) -> str: + """Return the universe domain used by the client.""" + universe_domain = 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 + + +try: + from google.api_core.gapic_v1.config import ( + use_client_cert_effective, + get_client_cert_source, + read_environment_variables, + ) +except ImportError: + from google.auth.transport import mtls # type: ignore + + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + + def use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + if hasattr(mtls, "should_use_client_cert"): + return mtls.should_use_client_cert() + else: + use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() + if use_client_cert_str not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" + " either `true` or `false`" + ) + return use_client_cert_str == "true" + + def get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return 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 ( + hasattr(mtls, "has_default_client_cert_source") + and mtls.has_default_client_cert_source() + ): + client_cert_source = mtls.default_client_cert_source() + else: + raise ValueError( + "Client certificate is required for mTLS, but no client certificate source was provided or found." + ) + return client_cert_source + + def read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + use_client_cert = use_client_cert_effective() + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() + universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") + 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, use_mtls_endpoint, universe_domain_env + + +try: + from google.api_core.gapic_v1.request import setup_request_id # type: ignore +except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. + def setup_request_id(request, field_name: str, is_proto3_optional: bool): + """Populate a UUID4 field in the request if it is not already set. + + Args: + request (Union[google.protobuf.message.Message, dict]): The request object. + field_name (str): The name of the field to populate. + is_proto3_optional (bool): Whether the field is proto3 optional. + """ + request_id_val = str(uuid.uuid4()) + if request is None: + return + + if isinstance(request, dict): + if is_proto3_optional: + if field_name not in request or request[field_name] is None: + request[field_name] = request_id_val + elif not request.get(field_name): + request[field_name] = request_id_val + return + + if is_proto3_optional: + try: + # Pure protobuf messages + if not request.HasField(field_name): + setattr(request, field_name, request_id_val) + except (AttributeError, ValueError): + # Proto-plus messages or other objects + if getattr(request, field_name, None) is None: + setattr(request, field_name, request_id_val) + else: + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) + + +try: + from google.api_core.rest_helpers import ( # type: ignore + flatten_query_params, + transcode_request as _core_transcode_request, + ) + import inspect + if "rest_numeric_enums" not in inspect.signature(_core_transcode_request).parameters: + raise ImportError + transcode_request = _core_transcode_request +except (ImportError, AttributeError): # pragma: NO COVER + # TODO: Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + from google.protobuf import json_format # type: ignore + from google.api_core import path_template # type: ignore + + def flatten_query_params(obj, strict=False): # pragma: NO COVER + if obj is not None and not isinstance(obj, dict): + raise TypeError("flatten_query_params must be called with dict object") + return _flatten(obj, key_path=[], strict=strict) + + def _flatten(obj, key_path, strict=False): # pragma: NO COVER + if obj is None: + return [] + if isinstance(obj, dict): + return _flatten_dict(obj, key_path=key_path, strict=strict) + if isinstance(obj, list): + return _flatten_list(obj, key_path=key_path, strict=strict) + return _flatten_value(obj, key_path=key_path, strict=strict) + + def _is_primitive_value(obj): # pragma: NO COVER + if obj is None: + return False + if isinstance(obj, (list, dict)): + raise ValueError("query params may not contain repeated dicts or lists") + return True + + def _flatten_value(obj, key_path, strict=False): # pragma: NO COVER + return [(".".join(key_path), _canonicalize(obj, strict=strict))] + + def _flatten_dict(obj, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten(value, key_path=key_path + [key], strict=strict) + for key, value in obj.items() + ) + return functools.reduce(operator.concat, items, []) # type: ignore + + def _flatten_list(elems, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten_value(elem, key_path=key_path, strict=strict) + for elem in elems + if _is_primitive_value(elem) + ) + return functools.reduce(operator.concat, items, []) # type: ignore + + def _canonicalize(obj, strict=False): # pragma: NO COVER + if strict: + value = str(obj) + if isinstance(obj, bool): + value = value.lower() + return value + return obj + + def transcode_request( # pragma: NO COVER + http_options: List[Dict[str, str]], + request: Any, + required_fields_default_values: Optional[Dict[str, Any]] = None, + rest_numeric_enums: bool = False, + ) -> Tuple[Dict[str, Any], Optional[str], Dict[str, Any]]: + pb_request = getattr(request, "_pb", request) + transcoded_request = path_template.transcode(http_options, pb_request) + + body_json = None + if transcoded_request.get("body") is not None: + body_json = json_format.MessageToJson( + transcoded_request["body"], + use_integers_for_enums=rest_numeric_enums, + ) + + query_params_json = {} + if transcoded_request.get("query_params") is not None: + query_params_json = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + use_integers_for_enums=rest_numeric_enums, + ) + ) + + if required_fields_default_values: + matched_option = None + for option in http_options: + if option.get("method", "").lower() == transcoded_request.get("method", "").lower(): + if path_template.validate( + option.get("uri", ""), transcoded_request.get("uri", "") + ): + matched_option = option + break + + bound_fields = set() + if matched_option: + uri_template = matched_option.get("uri", "") + for m in path_template._VARIABLE_RE.finditer(uri_template): + name = m.group("name") + if name: + bound_fields.add(name.split(".")[0]) + body_param = matched_option.get("body") + if body_param: + if body_param == "*": + bound_fields = None + else: + bound_fields.add(body_param.split(".")[0]) + + if bound_fields is not None: + for k, v in required_fields_default_values.items(): + if k in bound_fields: + continue + if k not in query_params_json: + query_params_json[k] = v + + if rest_numeric_enums: + query_params_json["$alt"] = "json;enum-encoding=int" + + return transcoded_request, body_json, query_params_json diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/transports/rest.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/transports/rest.py index 06ea5eab316d..7af48cf9f42f 100755 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/transports/rest.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/transports/rest.py @@ -20,7 +20,7 @@ from google.auth import credentials as ga_credentials # type: ignore from google.api_core import exceptions as core_exceptions from google.api_core import retry as retries -from google.api_core import rest_helpers +from google.cloud.storagebatchoperations_v1 import _compat as rest_helpers from google.api_core import rest_streaming from google.api_core import gapic_v1 import google.protobuf @@ -691,14 +691,17 @@ def __call__(self, """ http_options = _BaseStorageBatchOperationsRestTransport._BaseCancelJob._get_http_options() - request, metadata = self._interceptor.pre_cancel_job(request, metadata) - transcoded_request = _BaseStorageBatchOperationsRestTransport._BaseCancelJob._get_transcoded_request(http_options, request) - - body = _BaseStorageBatchOperationsRestTransport._BaseCancelJob._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseStorageBatchOperationsRestTransport._BaseCancelJob._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseStorageBatchOperationsRestTransport._BaseCancelJob, + "_BaseCancelJob__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -816,14 +819,17 @@ def __call__(self, """ http_options = _BaseStorageBatchOperationsRestTransport._BaseCreateJob._get_http_options() - request, metadata = self._interceptor.pre_create_job(request, metadata) - transcoded_request = _BaseStorageBatchOperationsRestTransport._BaseCreateJob._get_transcoded_request(http_options, request) - - body = _BaseStorageBatchOperationsRestTransport._BaseCreateJob._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseStorageBatchOperationsRestTransport._BaseCreateJob._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseStorageBatchOperationsRestTransport._BaseCreateJob, + "_BaseCreateJob__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -931,12 +937,17 @@ def __call__(self, """ http_options = _BaseStorageBatchOperationsRestTransport._BaseDeleteJob._get_http_options() - request, metadata = self._interceptor.pre_delete_job(request, metadata) - transcoded_request = _BaseStorageBatchOperationsRestTransport._BaseDeleteJob._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseStorageBatchOperationsRestTransport._BaseDeleteJob._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseStorageBatchOperationsRestTransport._BaseDeleteJob, + "_BaseDeleteJob__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1023,12 +1034,17 @@ def __call__(self, """ http_options = _BaseStorageBatchOperationsRestTransport._BaseGetBucketOperation._get_http_options() - request, metadata = self._interceptor.pre_get_bucket_operation(request, metadata) - transcoded_request = _BaseStorageBatchOperationsRestTransport._BaseGetBucketOperation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseStorageBatchOperationsRestTransport._BaseGetBucketOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseStorageBatchOperationsRestTransport._BaseGetBucketOperation, + "_BaseGetBucketOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1144,12 +1160,17 @@ def __call__(self, """ http_options = _BaseStorageBatchOperationsRestTransport._BaseGetJob._get_http_options() - request, metadata = self._interceptor.pre_get_job(request, metadata) - transcoded_request = _BaseStorageBatchOperationsRestTransport._BaseGetJob._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseStorageBatchOperationsRestTransport._BaseGetJob._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseStorageBatchOperationsRestTransport._BaseGetJob, + "_BaseGetJob__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1266,12 +1287,17 @@ def __call__(self, """ http_options = _BaseStorageBatchOperationsRestTransport._BaseListBucketOperations._get_http_options() - request, metadata = self._interceptor.pre_list_bucket_operations(request, metadata) - transcoded_request = _BaseStorageBatchOperationsRestTransport._BaseListBucketOperations._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseStorageBatchOperationsRestTransport._BaseListBucketOperations._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseStorageBatchOperationsRestTransport._BaseListBucketOperations, + "_BaseListBucketOperations__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1385,12 +1411,17 @@ def __call__(self, """ http_options = _BaseStorageBatchOperationsRestTransport._BaseListJobs._get_http_options() - request, metadata = self._interceptor.pre_list_jobs(request, metadata) - transcoded_request = _BaseStorageBatchOperationsRestTransport._BaseListJobs._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseStorageBatchOperationsRestTransport._BaseListJobs._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseStorageBatchOperationsRestTransport._BaseListJobs, + "_BaseListJobs__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1564,12 +1595,17 @@ def __call__(self, """ http_options = _BaseStorageBatchOperationsRestTransport._BaseGetLocation._get_http_options() - request, metadata = self._interceptor.pre_get_location(request, metadata) - transcoded_request = _BaseStorageBatchOperationsRestTransport._BaseGetLocation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseStorageBatchOperationsRestTransport._BaseGetLocation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseStorageBatchOperationsRestTransport._BaseGetLocation, + "_BaseGetLocation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1682,12 +1718,17 @@ def __call__(self, """ http_options = _BaseStorageBatchOperationsRestTransport._BaseListLocations._get_http_options() - request, metadata = self._interceptor.pre_list_locations(request, metadata) - transcoded_request = _BaseStorageBatchOperationsRestTransport._BaseListLocations._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseStorageBatchOperationsRestTransport._BaseListLocations._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseStorageBatchOperationsRestTransport._BaseListLocations, + "_BaseListLocations__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1798,14 +1839,17 @@ def __call__(self, """ http_options = _BaseStorageBatchOperationsRestTransport._BaseCancelOperation._get_http_options() - request, metadata = self._interceptor.pre_cancel_operation(request, metadata) - transcoded_request = _BaseStorageBatchOperationsRestTransport._BaseCancelOperation._get_transcoded_request(http_options, request) - - body = _BaseStorageBatchOperationsRestTransport._BaseCancelOperation._get_request_body_json(transcoded_request) - - # Jsonify the query params - query_params = _BaseStorageBatchOperationsRestTransport._BaseCancelOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseStorageBatchOperationsRestTransport._BaseCancelOperation, + "_BaseCancelOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1892,12 +1936,17 @@ def __call__(self, """ http_options = _BaseStorageBatchOperationsRestTransport._BaseDeleteOperation._get_http_options() - request, metadata = self._interceptor.pre_delete_operation(request, metadata) - transcoded_request = _BaseStorageBatchOperationsRestTransport._BaseDeleteOperation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseStorageBatchOperationsRestTransport._BaseDeleteOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseStorageBatchOperationsRestTransport._BaseDeleteOperation, + "_BaseDeleteOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -1987,12 +2036,17 @@ def __call__(self, """ http_options = _BaseStorageBatchOperationsRestTransport._BaseGetOperation._get_http_options() - request, metadata = self._interceptor.pre_get_operation(request, metadata) - transcoded_request = _BaseStorageBatchOperationsRestTransport._BaseGetOperation._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseStorageBatchOperationsRestTransport._BaseGetOperation._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseStorageBatchOperationsRestTransport._BaseGetOperation, + "_BaseGetOperation__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) @@ -2105,12 +2159,17 @@ def __call__(self, """ http_options = _BaseStorageBatchOperationsRestTransport._BaseListOperations._get_http_options() - request, metadata = self._interceptor.pre_list_operations(request, metadata) - transcoded_request = _BaseStorageBatchOperationsRestTransport._BaseListOperations._get_transcoded_request(http_options, request) - - # Jsonify the query params - query_params = _BaseStorageBatchOperationsRestTransport._BaseListOperations._get_query_params_json(transcoded_request) + transcoded_request, body, query_params = rest_helpers.transcode_request( + http_options, + request, + required_fields_default_values=getattr( + _BaseStorageBatchOperationsRestTransport._BaseListOperations, + "_BaseListOperations__REQUIRED_FIELDS_DEFAULT_VALUES", + None, + ), + rest_numeric_enums=False, + ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(logging.DEBUG): # pragma: NO COVER request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri']) diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/transports/rest_base.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/transports/rest_base.py index 280692aac74f..31006f98d738 100755 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/transports/rest_base.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/transports/rest_base.py @@ -99,7 +99,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -110,31 +109,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = storage_batch_operations.CancelJobRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseStorageBatchOperationsRestTransport._BaseCancelJob._get_unset_required_fields(query_params)) - - return query_params - class _BaseCreateJob: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -145,7 +119,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -156,31 +129,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = storage_batch_operations.CreateJobRequest.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=False - ) - 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=False, - )) - query_params.update(_BaseStorageBatchOperationsRestTransport._BaseCreateJob._get_unset_required_fields(query_params)) - - return query_params - class _BaseDeleteJob: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -191,7 +139,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -201,22 +148,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = storage_batch_operations.DeleteJobRequest.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=False, - )) - query_params.update(_BaseStorageBatchOperationsRestTransport._BaseDeleteJob._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetBucketOperation: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -227,7 +158,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -237,22 +167,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = storage_batch_operations.GetBucketOperationRequest.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=False, - )) - query_params.update(_BaseStorageBatchOperationsRestTransport._BaseGetBucketOperation._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetJob: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -263,7 +177,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -273,22 +186,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = storage_batch_operations.GetJobRequest.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=False, - )) - query_params.update(_BaseStorageBatchOperationsRestTransport._BaseGetJob._get_unset_required_fields(query_params)) - - return query_params - class _BaseListBucketOperations: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -299,7 +196,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -309,22 +205,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = storage_batch_operations.ListBucketOperationsRequest.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=False, - )) - query_params.update(_BaseStorageBatchOperationsRestTransport._BaseListBucketOperations._get_unset_required_fields(query_params)) - - return query_params - class _BaseListJobs: def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -335,7 +215,6 @@ def __hash__(self): # pragma: NO COVER @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]] = [{ @@ -345,23 +224,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - pb_request = storage_batch_operations.ListJobsRequest.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=False, - )) - query_params.update(_BaseStorageBatchOperationsRestTransport._BaseListJobs._get_unset_required_fields(query_params)) - - return query_params - class _BaseGetLocation: + """Base class for GetLocation REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -374,19 +238,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseListLocations: + """Base class for ListLocations REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -399,19 +252,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseCancelOperation: + """Base class for CancelOperation REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -425,23 +267,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_request_body_json(transcoded_request): - body = json.dumps(transcoded_request['body']) - return body - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseDeleteOperation: + """Base class for DeleteOperation REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -454,19 +281,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseGetOperation: + """Base class for GetOperation REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -479,19 +295,8 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - class _BaseListOperations: + """Base class for ListOperations REST mixin method.""" def __hash__(self): # pragma: NO COVER return NotImplementedError("__hash__ must be implemented.") @@ -504,18 +309,6 @@ def _get_http_options(): ] return http_options - @staticmethod - def _get_transcoded_request(http_options, request): - request_kwargs = json_format.MessageToDict(request) - transcoded_request = path_template.transcode( - http_options, **request_kwargs) - return transcoded_request - - @staticmethod - def _get_query_params_json(transcoded_request): - query_params = json.loads(json.dumps(transcoded_request['query_params'])) - return query_params - __all__=( '_BaseStorageBatchOperationsRestTransport', diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/tests/unit/gapic/storagebatchoperations_v1/test_storage_batch_operations.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/tests/unit/gapic/storagebatchoperations_v1/test_storage_batch_operations.py index 5c53e97f8d12..892375775385 100755 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/tests/unit/gapic/storagebatchoperations_v1/test_storage_batch_operations.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/tests/unit/gapic/storagebatchoperations_v1/test_storage_batch_operations.py @@ -776,7 +776,7 @@ def test_storage_batch_operations_client_get_mtls_endpoint_and_cert_source(clien config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -823,7 +823,7 @@ def test_storage_batch_operations_client_get_mtls_endpoint_and_cert_source(clien config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): diff --git a/packages/gapic-generator/tests/system/test_pqc.py b/packages/gapic-generator/tests/system/test_pqc.py index 2d694b6528f0..5d99b2b70225 100644 --- a/packages/gapic-generator/tests/system/test_pqc.py +++ b/packages/gapic-generator/tests/system/test_pqc.py @@ -45,9 +45,7 @@ def test_pqc_grpc(intercepted_echo_grpc): """Verifies that the gRPC client library negotiates post-quantum MLKEM with Showcase server.""" # TODO(https://github.com/googleapis/google-cloud-python/issues/17752): # Remove this check once grpcio >= 1.83.0 is enforced across all client libraries. - if Version(grpc.__version__) < Version("1.83.0rc0"): - # TODO(https://github.com/googleapis/google-cloud-python/issues/17751): - # Update the version in the check above to `1.83.0` once released. + if Version(grpc.__version__) < Version("1.83.0"): pytest.skip(f"gRPC PQC negotiation requires grpcio >= 1.83.0 (current: {grpc.__version__})") client, interceptor = intercepted_echo_grpc @@ -69,9 +67,7 @@ async def test_pqc_grpc_async(intercepted_echo_grpc_async): """Verifies that the async gRPC client library negotiates post-quantum MLKEM with Showcase server.""" # TODO(https://github.com/googleapis/google-cloud-python/issues/17752): # Remove this check once grpcio >= 1.83.0 is enforced across all client libraries. - if Version(grpc.__version__) < Version("1.83.0rc0"): - # TODO(https://github.com/googleapis/google-cloud-python/issues/17751): - # Update the version in the check above to `1.83.0` once released. + if Version(grpc.__version__) < Version("1.83.0"): pytest.skip( f"gRPC PQC negotiation requires grpcio >= 1.83.0 (current: {grpc.__version__})" ) diff --git a/packages/gapic-generator/tests/unit/generator/test_generator.py b/packages/gapic-generator/tests/unit/generator/test_generator.py index 9d8545c4192f..f1566ee6e5a7 100644 --- a/packages/gapic-generator/tests/unit/generator/test_generator.py +++ b/packages/gapic-generator/tests/unit/generator/test_generator.py @@ -117,6 +117,8 @@ def test_get_response_ignores_private_files(): list_templates.return_value = [ "foo/bar/baz.py.j2", "foo/bar/_base.py.j2", + "foo/bar/__init__.py.j2", + "foo/bar/_compat.py.j2", "molluscs/squid/sample.py.j2", ] with mock.patch.object(jinja2.Environment, "get_template") as get_template: @@ -128,12 +130,13 @@ def test_get_response_ignores_private_files(): get_template.assert_has_calls( [ mock.call("molluscs/squid/sample.py.j2"), + mock.call("foo/bar/__init__.py.j2"), + mock.call("foo/bar/_compat.py.j2"), mock.call("foo/bar/baz.py.j2"), - ] + ], + any_order=True, ) - assert len(cgr.file) == 1 - assert cgr.file[0].name == "foo/bar/baz.py" - assert cgr.file[0].content == "I am a template result.\n" + assert len(cgr.file) == 3 def test_get_response_fails_invalid_file_paths(): diff --git a/packages/gcp-sphinx-docfx-yaml/noxfile.py b/packages/gcp-sphinx-docfx-yaml/noxfile.py index ba5658c0e24f..b5a554a7bfbe 100644 --- a/packages/gcp-sphinx-docfx-yaml/noxfile.py +++ b/packages/gcp-sphinx-docfx-yaml/noxfile.py @@ -13,11 +13,24 @@ # limitations under the License. import os +import pathlib import nox DEFAULT_PYTHON_VERSION = "3.14" UNIT_TEST_PYTHON_VERSIONS = ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] +CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) + RUFF_VERSION = "ruff==0.14.14" ALL_PYTHON = list(UNIT_TEST_PYTHON_VERSIONS) @@ -49,6 +62,21 @@ def mypy(session): # Add mypy tests session.skip("mypy tests are not yet supported") + session.install("-e", ".") + session.install( + "mypy", + "types-setuptools", + "types-protobuf", + "types-requests", + ) + session.run( + "mypy", + f"--config-file={MYPY_CONFIG_FILE}", + "-p", + "docfx_yaml", + *session.posargs, + ) + @nox.session(python=DEFAULT_PYTHON_VERSION) def core_deps_from_source(session): diff --git a/packages/google-api-core/CHANGELOG.md b/packages/google-api-core/CHANGELOG.md index 40cc3e705500..f5ec0efc6ba0 100644 --- a/packages/google-api-core/CHANGELOG.md +++ b/packages/google-api-core/CHANGELOG.md @@ -4,6 +4,19 @@ [1]: https://pypi.org/project/google-api-core/#history +## [2.33.0](https://github.com/googleapis/google-cloud-python/compare/google-api-core-v2.32.0...google-api-core-v2.33.0) (2026-07-22) + + +### Features + +* **api_core:** add request-id auto-population logic to gapic_v1 public helpers ([#17738](https://github.com/googleapis/google-cloud-python/issues/17738)) ([68e1313](https://github.com/googleapis/google-cloud-python/commit/68e131341bd5d20ee50d7be3ed0410af4c12a46d)) +* **api-core:** add get_universe_domain helper to universe.py ([#17799](https://github.com/googleapis/google-cloud-python/issues/17799)) ([d461da7](https://github.com/googleapis/google-cloud-python/commit/d461da7bde04f2a698469f413116873633aa1f12)) + + +### Bug Fixes + +* **api-core:** prevent overwriting explicit empty strings for optional request_id ([#17798](https://github.com/googleapis/google-cloud-python/issues/17798)) ([07f7503](https://github.com/googleapis/google-cloud-python/commit/07f7503437f495dbce19b4f346ea17efcd738a65)) + ## [2.32.0](https://github.com/googleapis/google-cloud-python/compare/google-api-core-v2.31.0...google-api-core-v2.32.0) (2026-07-16) diff --git a/packages/google-api-core/google/api_core/_feature_gating_helpers.py b/packages/google-api-core/google/api_core/_feature_gating_helpers.py new file mode 100644 index 000000000000..0cf9bed7b5ce --- /dev/null +++ b/packages/google-api-core/google/api_core/_feature_gating_helpers.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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. +# + +"""Observability environment variable and client options resolution helpers.""" + +import os +import warnings +from typing import Any + +# We import ClientOptions for type hinting +from google.api_core.client_options import ClientOptions + +# Allowed truthy and falsy patterns for environment variables +_TRUTHY_VALUES = ("y", "yes", "t", "true", "on", "1") +_FALSY_VALUES = ("n", "no", "f", "false", "off", "0") + + +class FeatureGatingError(ValueError): + """Raised when feature gating resolution fails or is misconfigured.""" + + pass + + +def _strtobool(val: str) -> bool | None: + """Convert a string representation of truth to a boolean.""" + clean_val = val.lower().strip() + if not clean_val: + return None + if clean_val in _TRUTHY_VALUES: + return True + if clean_val in _FALSY_VALUES: + return False + raise ValueError(f"Invalid truth value: {val!r}") + + +def _get_env_bool(name: str) -> bool | None: + """Retrieve the boolean value of an environment variable.""" + val = os.getenv(name) + if val is None: + return None + try: + return _strtobool(val) + except ValueError as e: + warnings.warn(f"Ignored invalid value for {name}: {e}", RuntimeWarning) + return None + + +def _has_feature_key( + *, configuration: ClientOptions | dict[str, Any] | None, feature_key: str +) -> bool: + """Checks if a specific feature key is present and not None in configuration.""" + if configuration is None: + return False + + if feature_key.startswith("__"): + return False + + if isinstance(configuration, dict): + return configuration.get(feature_key) is not None + + return getattr(configuration, feature_key, None) is not None + + +def resolve_feature_flags( + *, + env_var: str, + feature_key: str, + configuration: ClientOptions | dict[str, Any] | None = None, +) -> bool: + """Determines if a feature is enabled based on environment variables and configuration. + + Behavior depends on whether the `env_var` name contains "EXPERIMENTAL": + + - **Experimental Path** (env_var contains "EXPERIMENTAL"): + Strict control. Requires the environment variable to be explicitly 'true'. + If a programmatic feature key is passed but the environment variable is not 'true', + raises FeatureGatingError (Fail Fast). + + - **GA Path** (env_var does not contain "EXPERIMENTAL"): + Standard precedence. Enabled if a programmatic feature key is passed, + otherwise falls back to the environment variable value. + + Args: + env_var: The name of the environment variable controlling this feature. + feature_key: The key in configuration/attributes for the programmatic configuration. + configuration: Optional. A dictionary or object containing client configuration. + + Returns: + bool: True if the feature is resolved to enabled, False otherwise. + + Raises: + FeatureGatingError: If a feature key is provided for an experimental feature without enabling the experimental environment variable. + """ + + # Check for programmatic feature configuration + has_feature_key = _has_feature_key( + configuration=configuration, feature_key=feature_key + ) + + # Read environment variable + env_var_setting = _get_env_bool(env_var) + + # EXPERIMENTAL PATH: + # Resolution Hierarchy: + # 1. EXPERIMENTAL Environment Variable + # 2. Fail Fast if Feature Key present but EXPERIMENTAL Environment Variable is not enabled + if "EXPERIMENTAL" in env_var: + # Fail Fast if feature key present but experimental environment variable is not enabled + if env_var_setting is not True and has_feature_key: + raise FeatureGatingError( + f"Experimental feature requires {env_var} to be set to 'true' to use programmatic configuration." + ) + + return bool(env_var_setting) + + # GENERAL AVAILABILITY PATH: + # Resolution Hierarchy: + # 1. Programmatic Configuration (Feature Key) + # 2. Environment Variable + + # Check Programmatic Configuration + if has_feature_key: + return True + + # Check Environment Variable + return bool(env_var_setting) diff --git a/packages/google-api-core/google/api_core/version.py b/packages/google-api-core/google/api_core/version.py index b8c5af9a24bc..17811b65f86e 100644 --- a/packages/google-api-core/google/api_core/version.py +++ b/packages/google-api-core/google/api_core/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.32.0" +__version__ = "2.33.0" diff --git a/packages/google-api-core/noxfile.py b/packages/google-api-core/noxfile.py index 4ab3143b1894..a696331a73bb 100644 --- a/packages/google-api-core/noxfile.py +++ b/packages/google-api-core/noxfile.py @@ -36,6 +36,16 @@ DEFAULT_PYTHON_VERSION = "3.14" CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) # Error if a python version is missing @@ -378,7 +388,7 @@ def mypy(session): "types-requests", "types-protobuf", ) - session.run("mypy", "google", "tests") + session.run("mypy", f"--config-file={MYPY_CONFIG_FILE}", "google", "tests") @nox.session(python=DEFAULT_PYTHON_VERSION) diff --git a/packages/google-api-core/tests/unit/test_feature_gating_helpers.py b/packages/google-api-core/tests/unit/test_feature_gating_helpers.py new file mode 100644 index 000000000000..9a8f0886442c --- /dev/null +++ b/packages/google-api-core/tests/unit/test_feature_gating_helpers.py @@ -0,0 +1,224 @@ +# Copyright 2026 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 pytest +from google.api_core import _feature_gating_helpers +from google.api_core._feature_gating_helpers import ( + FeatureGatingError, + _get_env_bool, + _strtobool, +) + + +@pytest.mark.parametrize( + "value,expected", + [ + # Truthy values + ("y", True), + ("yes", True), + ("t", True), + ("true", True), + ("on", True), + ("1", True), + (" True ", True), + # Falsy values + ("n", False), + ("no", False), + ("f", False), + ("false", False), + ("off", False), + ("0", False), + (" FALSE ", False), + # Empty string + ("", None), + ], +) +def test_strtobool(value, expected): + assert _strtobool(value) is expected + + +def test_strtobool_invalid(): + with pytest.raises(ValueError): + _strtobool("invalid") + + +def test_get_env_bool(monkeypatch): + monkeypatch.setenv("TEST_VAR", "true") + assert _get_env_bool("TEST_VAR") is True + + monkeypatch.setenv("TEST_VAR", "invalid") + import pytest + + with pytest.warns(RuntimeWarning, match="Ignored invalid value"): + assert _get_env_bool("TEST_VAR") is None + + monkeypatch.delenv("TEST_VAR", raising=False) + assert _get_env_bool("TEST_VAR") is None + + +def test_resolve_feature_flags_ga_enabled_via_env(monkeypatch): + """Verify that a GA feature is enabled if its environment variable is True.""" + # Setup: We pass a GA environment variable set to True + monkeypatch.setenv("GOOGLE_SDK_PYTHON_TRACING_ENABLED", "true") + + # Action + result = _feature_gating_helpers.resolve_feature_flags( + env_var="GOOGLE_SDK_PYTHON_TRACING_ENABLED", + feature_key="tracer_provider", + configuration=None, + ) + + # Assertion + assert result is True + + +@pytest.mark.parametrize("exp_env_state", [None, "false"], ids=["missing", "disabled"]) +def test_resolve_feature_flags_exp_blocked_with_feature_key_fails_fast( + monkeypatch, exp_env_state +): + """Verify that passing a feature_key to an experimental feature raises FeatureGatingError if the experimental environment variable is disabled or missing.""" + # Setup: Experimental env var is set to exp_env_state (None means not set) + if exp_env_state is not None: + monkeypatch.setenv( + "GOOGLE_SDK_EXPERIMENTAL_PYTHON_TRACING_ENABLED", exp_env_state + ) + else: + monkeypatch.delenv( + "GOOGLE_SDK_EXPERIMENTAL_PYTHON_TRACING_ENABLED", raising=False + ) + configuration = {"tracer_provider": object()} + + # Action & Assertion + with pytest.raises(FeatureGatingError, match="Experimental feature"): + _feature_gating_helpers.resolve_feature_flags( + env_var="GOOGLE_SDK_EXPERIMENTAL_PYTHON_TRACING_ENABLED", + feature_key="tracer_provider", + configuration=configuration, + ) + + +def test_resolve_feature_flags_exp_enabled_with_feature_key(monkeypatch): + """Verify that experimental feature is enabled if the experimental environment variable is enabled and a feature_key is provided.""" + monkeypatch.setenv("GOOGLE_SDK_EXPERIMENTAL_PYTHON_TRACING_ENABLED", "true") + configuration = {"tracer_provider": object()} + + result = _feature_gating_helpers.resolve_feature_flags( + env_var="GOOGLE_SDK_EXPERIMENTAL_PYTHON_TRACING_ENABLED", + feature_key="tracer_provider", + configuration=configuration, + ) + assert result is True + + +def test_resolve_feature_flags_exp_enabled_without_feature_key(monkeypatch): + """Verify that experimental feature is enabled if the experimental environment variable is enabled and NO feature_key is provided.""" + monkeypatch.setenv("GOOGLE_SDK_EXPERIMENTAL_PYTHON_TRACING_ENABLED", "true") + + result = _feature_gating_helpers.resolve_feature_flags( + env_var="GOOGLE_SDK_EXPERIMENTAL_PYTHON_TRACING_ENABLED", + feature_key="tracer_provider", + configuration=None, + ) + assert result is True + + +def test_resolve_feature_flags_exp_disabled_without_feature_key(monkeypatch): + """Verify that experimental feature is disabled if the experimental environment variable is disabled and NO feature_key is provided.""" + monkeypatch.setenv("GOOGLE_SDK_EXPERIMENTAL_PYTHON_TRACING_ENABLED", "false") + + result = _feature_gating_helpers.resolve_feature_flags( + env_var="GOOGLE_SDK_EXPERIMENTAL_PYTHON_TRACING_ENABLED", + feature_key="tracer_provider", + configuration=None, + ) + assert result is False + + +def test_resolve_feature_flags_ga_enabled_via_feature_key(monkeypatch): + """Verify that a GA feature is enabled if a feature_key is provided, ignoring the environment variable.""" + # Env var is False, but feature_key is present in configuration + monkeypatch.setenv("GOOGLE_SDK_PYTHON_TRACING_ENABLED", "false") + configuration = {"tracer_provider": object()} + + result = _feature_gating_helpers.resolve_feature_flags( + env_var="GOOGLE_SDK_PYTHON_TRACING_ENABLED", + feature_key="tracer_provider", + configuration=configuration, + ) + assert result is True + + +@pytest.mark.parametrize( + "env_val", [None, "false"], ids=["env_not_set", "env_explicit_false"] +) +def test_resolve_feature_flags_ga_fallback_to_false(monkeypatch, env_val): + """Verify that a GA feature is disabled if neither a feature_key is provided nor the environment variable is enabled.""" + if env_val is not None: + monkeypatch.setenv("GOOGLE_SDK_PYTHON_TRACING_ENABLED", env_val) + else: + monkeypatch.delenv("GOOGLE_SDK_PYTHON_TRACING_ENABLED", raising=False) + result = _feature_gating_helpers.resolve_feature_flags( + env_var="GOOGLE_SDK_PYTHON_TRACING_ENABLED", + feature_key="tracer_provider", + configuration=None, + ) + assert result is False + + +class _MockOptions: + def __init__(self): + self.other_option = "value" + + +@pytest.mark.parametrize( + "configuration", + [ + {"other_option": "value"}, + _MockOptions(), + ], + ids=["dict_without_key", "object_without_key"], +) +def test_resolve_feature_flags_options_without_key(configuration): + """Verify behavior when configuration is present but missing the feature key.""" + # GA Path: should fall through to env var / fallback + result = _feature_gating_helpers.resolve_feature_flags( + env_var="GOOGLE_SDK_PYTHON_TRACING_ENABLED", + feature_key="tracer_provider", + configuration=configuration, + ) + assert result is False + + +def test_resolve_feature_flags_rejects_dunder_keys(monkeypatch): + """Verify that dunder keys are rejected early in _has_feature_key.""" + # We use a dunder key that exists on all objects (__class__) + # If the guardrail is missing, getattr might return it and return True + configuration = {"__class__": "some_value"} + + result = _feature_gating_helpers.resolve_feature_flags( + env_var="GOOGLE_SDK_PYTHON_TRACING_ENABLED", + feature_key="__class__", + configuration=configuration, + ) + assert result is False + + class MockWithClass: + __class__ = "some_value" + + result = _feature_gating_helpers.resolve_feature_flags( + env_var="GOOGLE_SDK_PYTHON_TRACING_ENABLED", + feature_key="__class__", + configuration=MockWithClass(), + ) + assert result is False diff --git a/packages/google-auth-httplib2/noxfile.py b/packages/google-auth-httplib2/noxfile.py index 67b1bd420cc5..162301d4f13b 100644 --- a/packages/google-auth-httplib2/noxfile.py +++ b/packages/google-auth-httplib2/noxfile.py @@ -69,6 +69,17 @@ SYSTEM_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = {} CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) + # Error if a python version is missing nox.options.error_on_missing_interpreters = True @@ -101,6 +112,7 @@ def mypy(session): session.install(".") session.run( "mypy", + f"--config-file={MYPY_CONFIG_FILE}", "google_auth_httplib2.py", ) diff --git a/packages/google-auth-oauthlib/noxfile.py b/packages/google-auth-oauthlib/noxfile.py index a028eb1f1900..1a7f043e7461 100644 --- a/packages/google-auth-oauthlib/noxfile.py +++ b/packages/google-auth-oauthlib/noxfile.py @@ -69,6 +69,17 @@ SYSTEM_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = {} CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) + # Error if a python version is missing nox.options.error_on_missing_interpreters = True @@ -471,6 +482,7 @@ def mypy(session): session.install(".") session.run( "mypy", + f"--config-file={MYPY_CONFIG_FILE}", "-p", "google_auth_oauthlib", ) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 18a656d59adc..1440d0d8de9e 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,16 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.56.2](https://github.com/googleapis/google-cloud-python/compare/google-auth-v2.56.1...google-auth-v2.56.2) (2026-07-21) + + +### Bug Fixes + +* **auth:** centralize cert discovery logic and steps ([#17696](https://github.com/googleapis/google-cloud-python/issues/17696)) ([edc0423](https://github.com/googleapis/google-cloud-python/commit/edc0423e57dd06bef3a93802642a074ab8b7bc5d)) +* **auth:** exit early when agent cert config is outside well-known directory ([#17762](https://github.com/googleapis/google-cloud-python/issues/17762)) ([61e795a](https://github.com/googleapis/google-cloud-python/commit/61e795a8299afec863487776c8a679adbb2911ab)) +* **transport:** propagate mTLS adapter to auth session and fix connection leaks ([#17689](https://github.com/googleapis/google-cloud-python/issues/17689)) ([8289d32](https://github.com/googleapis/google-cloud-python/commit/8289d328f9a1eb2abb766644e4f6748198679c80)) +* update _SERVICE_ACCOUNT_EMAIL_PATTERN to require .gserviceaccount.com suffix ([#17748](https://github.com/googleapis/google-cloud-python/issues/17748)) ([b60bb04](https://github.com/googleapis/google-cloud-python/commit/b60bb04a73eb35b15320c54bc3493132e6ba5706)) + ## [2.56.1](https://github.com/googleapis/google-cloud-python/compare/google-auth-v2.56.0...google-auth-v2.56.1) (2026-07-17) diff --git a/packages/google-auth/google/auth/aio/transport/sessions.py b/packages/google-auth/google/auth/aio/transport/sessions.py index cf7915a562dd..d88162667bda 100644 --- a/packages/google-auth/google/auth/aio/transport/sessions.py +++ b/packages/google-auth/google/auth/aio/transport/sessions.py @@ -192,14 +192,13 @@ async def _do_configure(): ) = await mtls.get_client_cert_and_key(client_cert_callback) if is_mtls: - ssl_context = await mtls._run_in_executor( - mtls.make_client_cert_ssl_context, cert, key - ) - # Re-create the auth request with the new SSL context if AIOHTTP_INSTALLED and isinstance( self._auth_request, AiohttpRequest ): + ssl_context = await mtls._run_in_executor( + mtls.make_client_cert_ssl_context, cert, key + ) connector = aiohttp.TCPConnector(ssl=ssl_context) new_session = aiohttp.ClientSession(connector=connector) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index f8e1769334d2..1ea7792c2cdd 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -38,7 +38,9 @@ _LOGGER = logging.getLogger(__name__) -_SERVICE_ACCOUNT_EMAIL_PATTERN = re.compile(r"^[^@]+@[^@]+\.[^@]+$") +_SERVICE_ACCOUNT_EMAIL_PATTERN = re.compile( + r"^[^@]+@[^@]+\.gserviceaccount\.com$", re.IGNORECASE +) _GCE_DEFAULT_MDS_IP = "169.254.169.254" _GCE_DEFAULT_HOST = "metadata.google.internal" diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 75f18956ea9c..fe79f2cd138a 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.56.1" +__version__ = "2.56.2" diff --git a/packages/google-auth/mypy.ini b/packages/google-auth/mypy.ini deleted file mode 100644 index c129006db138..000000000000 --- a/packages/google-auth/mypy.ini +++ /dev/null @@ -1,3 +0,0 @@ -[mypy] -python_version = 3.9 -namespace_packages = True diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 19cc47a02a03..38e5f4d82f70 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -20,6 +20,17 @@ import nox CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) + CLICK_VERSION = "click" BLACK_VERSION = "black==23.7.0" @@ -155,7 +166,16 @@ def mypy(session): "types-mock", "pytest<8.0.0", ) - session.run("mypy", "-p", "google", "-p", "tests", "-p", "tests_async") + session.run( + "mypy", + f"--config-file={MYPY_CONFIG_FILE}", + "-p", + "google", + "-p", + "tests", + "-p", + "tests_async", + ) @nox.session(python=ALL_PYTHON) diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index 199683f7b8c2..0fae4bd6ef16 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -993,9 +993,13 @@ def test__is_service_account_email(): _metadata._is_service_account_email("my-sa@my-project.iam.gserviceaccount.com") is True ) - assert _metadata._is_service_account_email("test@example.com") is True + assert ( + _metadata._is_service_account_email("my-sa@my-project.iam.GSERVICEACCOUNT.COM") + is True + ) # Empty inputs and standard string placeholders + assert _metadata._is_service_account_email("test@example.com") is False assert _metadata._is_service_account_email("default") is False assert _metadata._is_service_account_email("") is False assert _metadata._is_service_account_email(None) is False diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 8f8a17e94640..ab171c5a6041 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -46,7 +46,7 @@ ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE = "gl-python/ auth/ auth-request-type/at cred-type/mds" ID_TOKEN_REQUEST_METRICS_HEADER_VALUE = "gl-python/ auth/ auth-request-type/it cred-type/mds" -FAKE_SERVICE_ACCOUNT_EMAIL = "foo@bar.com" +FAKE_SERVICE_ACCOUNT_EMAIL = "foo@project.iam.gserviceaccount.com" FAKE_QUOTA_PROJECT_ID = "fake-quota-project" FAKE_SCOPES = ["scope1", "scope2"] FAKE_DEFAULT_SCOPES = ["scope3", "scope4"] @@ -98,7 +98,7 @@ def test_refresh_success(self, get, utcnow): get.side_effect = [ { # First request is for sevice account info. - "email": "service-account@example.com", + "email": "service-account@project.iam.gserviceaccount.com", "scopes": ["one", "two"], }, { @@ -116,7 +116,10 @@ def test_refresh_success(self, get, utcnow): assert self.credentials.expiry == (utcnow() + datetime.timedelta(seconds=500)) # Check the credential info - assert self.credentials.service_account_email == "service-account@example.com" + assert ( + self.credentials.service_account_email + == "service-account@project.iam.gserviceaccount.com" + ) assert self.credentials._scopes == ["one", "two"] # Check that the credentials are valid (have a token and are not @@ -136,7 +139,7 @@ def test_refresh_success_with_scopes(self, get, utcnow, mock_metrics_header_valu get.side_effect = [ { # First request is for sevice account info. - "email": "service-account@example.com", + "email": "service-account@project.iam.gserviceaccount.com", "scopes": ["one", "two"], }, { @@ -156,7 +159,10 @@ def test_refresh_success_with_scopes(self, get, utcnow, mock_metrics_header_valu assert self.credentials.expiry == (utcnow() + datetime.timedelta(seconds=500)) # Check the credential info - assert self.credentials.service_account_email == "service-account@example.com" + assert ( + self.credentials.service_account_email + == "service-account@project.iam.gserviceaccount.com" + ) assert self.credentials._scopes == scopes # Check that the credentials are valid (have a token and are not @@ -195,7 +201,7 @@ def test_before_request_refreshes(self, get): get.side_effect = [ { # First request is for sevice account info. - "email": "service-account@example.com", + "email": "service-account@project.iam.gserviceaccount.com", "scopes": "one two", }, { @@ -300,7 +306,7 @@ def test_build_regional_access_boundary_lookup_url_default_email( self, mock_get_service_account_info ): mock_get_service_account_info.return_value = { - "email": "resolved-email@example.com" + "email": "resolved-email@project.iam.gserviceaccount.com" } creds = self.credentials creds._universe_domain_cached = True @@ -308,15 +314,17 @@ def test_build_regional_access_boundary_lookup_url_default_email( url = creds._build_regional_access_boundary_lookup_url(request=mock_request) mock_get_service_account_info.assert_called_once_with(mock_request, "default") - expected_url_standard = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/resolved-email@example.com/allowedLocations" - expected_url_mtls = "https://iamcredentials.mtls.googleapis.com/v1/projects/-/serviceAccounts/resolved-email@example.com/allowedLocations" + expected_url_standard = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/resolved-email@project.iam.gserviceaccount.com/allowedLocations" + expected_url_mtls = "https://iamcredentials.mtls.googleapis.com/v1/projects/-/serviceAccounts/resolved-email@project.iam.gserviceaccount.com/allowedLocations" assert url in (expected_url_standard, expected_url_mtls) @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_build_regional_access_boundary_lookup_url_http_client_request( self, mock_get ): - mock_get.return_value = {"email": "resolved-email@example.com"} + mock_get.return_value = { + "email": "resolved-email@project.iam.gserviceaccount.com" + } creds = self.credentials creds._universe_domain_cached = True @@ -326,8 +334,8 @@ def test_build_regional_access_boundary_lookup_url_http_client_request( url = creds._build_regional_access_boundary_lookup_url(request=req) - expected_url_standard = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/resolved-email@example.com/allowedLocations" - expected_url_mtls = "https://iamcredentials.mtls.googleapis.com/v1/projects/-/serviceAccounts/resolved-email@example.com/allowedLocations" + expected_url_standard = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/resolved-email@project.iam.gserviceaccount.com/allowedLocations" + expected_url_mtls = "https://iamcredentials.mtls.googleapis.com/v1/projects/-/serviceAccounts/resolved-email@project.iam.gserviceaccount.com/allowedLocations" assert url in (expected_url_standard, expected_url_mtls) @mock.patch( @@ -352,7 +360,7 @@ def test_build_regional_access_boundary_lookup_url_explicit_email_standard( url = creds._build_regional_access_boundary_lookup_url() mock_get_service_account_info.assert_not_called() - expected_url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/foo@bar.com/allowedLocations" + expected_url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/foo@project.iam.gserviceaccount.com/allowedLocations" assert url == expected_url @mock.patch( @@ -377,7 +385,7 @@ def test_build_regional_access_boundary_lookup_url_explicit_email_mtls( url = creds._build_regional_access_boundary_lookup_url() mock_get_service_account_info.assert_not_called() - expected_url = "https://iamcredentials.mtls.googleapis.com/v1/projects/-/serviceAccounts/foo@bar.com/allowedLocations" + expected_url = "https://iamcredentials.mtls.googleapis.com/v1/projects/-/serviceAccounts/foo@project.iam.gserviceaccount.com/allowedLocations" assert url == expected_url @mock.patch( @@ -491,7 +499,10 @@ def test_refresh_with_agent_identity( mock_get_path.return_value = str(cert_path) mock_metadata_get.side_effect = [ - {"email": "service-account@example.com", "scopes": ["one", "two"]}, + { + "email": "service-account@project.iam.gserviceaccount.com", + "scopes": ["one", "two"], + }, {"access_token": "token", "expires_in": 500}, ] @@ -526,7 +537,10 @@ def test_refresh_with_agent_identity_opt_out_or_not_agent( mock_get_path.return_value = str(cert_path) mock_metadata_get.side_effect = [ - {"email": "service-account@example.com", "scopes": ["one", "two"]}, + { + "email": "service-account@project.iam.gserviceaccount.com", + "scopes": ["one", "two"], + }, {"access_token": "token", "expires_in": 500}, ] @@ -553,7 +567,10 @@ class TestIDTokenCredentials(object): @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_default_state(self, get): get.side_effect = [ - {"email": "service-account@example.com", "scope": ["one", "two"]} + { + "email": "service-account@project.iam.gserviceaccount.com", + "scope": ["one", "two"], + } ] request = mock.create_autospec(transport.Request, instance=True) @@ -565,10 +582,16 @@ def test_default_state(self, get): # Expiration hasn't been set yet assert not self.credentials.expired # Service account email hasn't been populated - assert self.credentials.service_account_email == "service-account@example.com" + assert ( + self.credentials.service_account_email + == "service-account@project.iam.gserviceaccount.com" + ) # Signer is initialized assert self.credentials.signer - assert self.credentials.signer_email == "service-account@example.com" + assert ( + self.credentials.signer_email + == "service-account@project.iam.gserviceaccount.com" + ) # No quota project assert not self.credentials._quota_project_id @@ -580,7 +603,10 @@ def test_default_state(self, get): @mock.patch("google.auth.iam.Signer.sign", autospec=True) def test_make_authorization_grant_assertion(self, sign, get, utcnow): get.side_effect = [ - {"email": "service-account@example.com", "scopes": ["one", "two"]} + { + "email": "service-account@project.iam.gserviceaccount.com", + "scopes": ["one", "two"], + } ] sign.side_effect = [b"signature"] @@ -601,7 +627,7 @@ def test_make_authorization_grant_assertion(self, sign, get, utcnow): "aud": "https://www.googleapis.com/oauth2/v4/token", "exp": 3600, "iat": 0, - "iss": "service-account@example.com", + "iss": "service-account@project.iam.gserviceaccount.com", "target_audience": "https://audience.com", } @@ -645,7 +671,10 @@ def test_with_service_account(self, sign, get, utcnow): @mock.patch("google.auth.iam.Signer.sign", autospec=True) def test_additional_claims(self, sign, get, utcnow): get.side_effect = [ - {"email": "service-account@example.com", "scopes": ["one", "two"]} + { + "email": "service-account@project.iam.gserviceaccount.com", + "scopes": ["one", "two"], + } ] sign.side_effect = [b"signature"] @@ -668,7 +697,7 @@ def test_additional_claims(self, sign, get, utcnow): "aud": "https://www.googleapis.com/oauth2/v4/token", "exp": 3600, "iat": 0, - "iss": "service-account@example.com", + "iss": "service-account@project.iam.gserviceaccount.com", "target_audience": "https://audience.com", "foo": "bar", } @@ -679,7 +708,7 @@ def test_token_uri(self): self.credentials = credentials.IDTokenCredentials( request=request, signer=mock.Mock(), - service_account_email="foo@example.com", + service_account_email="foo@project.iam.gserviceaccount.com", target_audience="https://audience.com", ) assert self.credentials._token_uri == credentials._DEFAULT_TOKEN_URI @@ -687,7 +716,7 @@ def test_token_uri(self): self.credentials = credentials.IDTokenCredentials( request=request, signer=mock.Mock(), - service_account_email="foo@example.com", + service_account_email="foo@project.iam.gserviceaccount.com", target_audience="https://audience.com", token_uri="https://example.com/token", ) @@ -701,7 +730,10 @@ def test_token_uri(self): @mock.patch("google.auth.iam.Signer.sign", autospec=True) def test_with_target_audience(self, sign, get, utcnow): get.side_effect = [ - {"email": "service-account@example.com", "scopes": ["one", "two"]} + { + "email": "service-account@project.iam.gserviceaccount.com", + "scopes": ["one", "two"], + } ] sign.side_effect = [b"signature"] @@ -723,7 +755,7 @@ def test_with_target_audience(self, sign, get, utcnow): "aud": "https://www.googleapis.com/oauth2/v4/token", "exp": 3600, "iat": 0, - "iss": "service-account@example.com", + "iss": "service-account@project.iam.gserviceaccount.com", "target_audience": "https://actually.not", } @@ -748,7 +780,7 @@ def test_with_target_audience_integration(self): content_type="application/json", json={ "scopes": "email", - "email": "service-account@example.com", + "email": "service-account@project.iam.gserviceaccount.com", "aliases": ["default"], }, ) @@ -792,7 +824,7 @@ def test_with_target_audience_integration(self): responses.POST, re.compile( r"https://iamcredentials\.(mtls\.)?googleapis\.com/v1/projects/-/" - r"serviceAccounts/service-account@example\.com:signBlob" + r"serviceAccounts/service-account@project\.iam\.gserviceaccount\.com:signBlob" ), status=200, content_type="application/json", @@ -816,7 +848,7 @@ def test_with_target_audience_integration(self): self.credentials = credentials.IDTokenCredentials( request=requests.Request(), - service_account_email="service-account@example.com", + service_account_email="service-account@project.iam.gserviceaccount.com", target_audience="https://audience.com", ) @@ -834,7 +866,10 @@ def test_with_target_audience_integration(self): @mock.patch("google.auth.iam.Signer.sign", autospec=True) def test_with_quota_project(self, sign, get, utcnow): get.side_effect = [ - {"email": "service-account@example.com", "scopes": ["one", "two"]} + { + "email": "service-account@project.iam.gserviceaccount.com", + "scopes": ["one", "two"], + } ] sign.side_effect = [b"signature"] @@ -858,7 +893,7 @@ def test_with_quota_project(self, sign, get, utcnow): "aud": "https://www.googleapis.com/oauth2/v4/token", "exp": 3600, "iat": 0, - "iss": "service-account@example.com", + "iss": "service-account@project.iam.gserviceaccount.com", "target_audience": "https://audience.com", } @@ -878,7 +913,10 @@ def test_with_quota_project(self, sign, get, utcnow): @mock.patch("google.auth.iam.Signer.sign", autospec=True) def test_with_token_uri(self, sign, get, utcnow): get.side_effect = [ - {"email": "service-account@example.com", "scopes": ["one", "two"]} + { + "email": "service-account@project.iam.gserviceaccount.com", + "scopes": ["one", "two"], + } ] sign.side_effect = [b"signature"] @@ -900,7 +938,10 @@ def test_with_token_uri(self, sign, get, utcnow): @mock.patch("google.auth.iam.Signer.sign", autospec=True) def test_with_token_uri_exception(self, sign, get, utcnow): get.side_effect = [ - {"email": "service-account@example.com", "scopes": ["one", "two"]} + { + "email": "service-account@project.iam.gserviceaccount.com", + "scopes": ["one", "two"], + } ] sign.side_effect = [b"signature"] @@ -932,7 +973,7 @@ def test_with_quota_project_integration(self): content_type="application/json", json={ "scopes": "email", - "email": "service-account@example.com", + "email": "service-account@project.iam.gserviceaccount.com", "aliases": ["default"], }, ) @@ -976,7 +1017,7 @@ def test_with_quota_project_integration(self): responses.POST, re.compile( r"https://iamcredentials\.(mtls\.)?googleapis\.com/v1/projects/-/" - r"serviceAccounts/service-account@example\.com:signBlob" + r"serviceAccounts/service-account@project\.iam\.gserviceaccount\.com:signBlob" ), status=200, content_type="application/json", @@ -1000,7 +1041,7 @@ def test_with_quota_project_integration(self): self.credentials = credentials.IDTokenCredentials( request=requests.Request(), - service_account_email="service-account@example.com", + service_account_email="service-account@project.iam.gserviceaccount.com", target_audience="https://audience.com", ) @@ -1020,7 +1061,10 @@ def test_with_quota_project_integration(self): @mock.patch("google.oauth2._client.id_token_jwt_grant", autospec=True) def test_refresh_success(self, id_token_jwt_grant, sign, get, utcnow): get.side_effect = [ - {"email": "service-account@example.com", "scopes": ["one", "two"]} + { + "email": "service-account@project.iam.gserviceaccount.com", + "scopes": ["one", "two"], + } ] sign.side_effect = [b"signature"] id_token_jwt_grant.side_effect = [ @@ -1044,7 +1088,10 @@ def test_refresh_success(self, id_token_jwt_grant, sign, get, utcnow): assert self.credentials.expiry == _helpers.utcfromtimestamp(3600) # Check the credential info - assert self.credentials.service_account_email == "service-account@example.com" + assert ( + self.credentials.service_account_email + == "service-account@project.iam.gserviceaccount.com" + ) # Check that the credentials are valid (have a token and are not # expired) @@ -1058,7 +1105,10 @@ def test_refresh_success(self, id_token_jwt_grant, sign, get, utcnow): @mock.patch("google.auth.iam.Signer.sign", autospec=True) def test_refresh_error(self, sign, get, utcnow): get.side_effect = [ - {"email": "service-account@example.com", "scopes": ["one", "two"]} + { + "email": "service-account@project.iam.gserviceaccount.com", + "scopes": ["one", "two"], + } ] sign.side_effect = [b"signature"] @@ -1086,7 +1136,10 @@ def test_refresh_error(self, sign, get, utcnow): @mock.patch("google.oauth2._client.id_token_jwt_grant", autospec=True) def test_before_request_refreshes(self, id_token_jwt_grant, sign, get, utcnow): get.side_effect = [ - {"email": "service-account@example.com", "scopes": "one two"} + { + "email": "service-account@project.iam.gserviceaccount.com", + "scopes": "one two", + } ] sign.side_effect = [b"signature"] id_token_jwt_grant.side_effect = [ @@ -1119,7 +1172,10 @@ def test_before_request_refreshes(self, id_token_jwt_grant, sign, get, utcnow): @mock.patch("google.auth.iam.Signer.sign", autospec=True) def test_sign_bytes(self, sign, get): get.side_effect = [ - {"email": "service-account@example.com", "scopes": ["one", "two"]} + { + "email": "service-account@project.iam.gserviceaccount.com", + "scopes": ["one", "two"], + } ] sign.side_effect = [b"signature"] @@ -1151,7 +1207,9 @@ def test_get_id_token_from_metadata( self, get, get_service_account_info, mock_metrics_header_value ): get.return_value = SAMPLE_ID_TOKEN - get_service_account_info.return_value = {"email": "foo@example.com"} + get_service_account_info.return_value = { + "email": "foo@project.iam.gserviceaccount.com" + } cred = credentials.IDTokenCredentials( mock.Mock(), "audience", use_metadata_identity_endpoint=True @@ -1167,7 +1225,7 @@ def test_get_id_token_from_metadata( assert cred._use_metadata_identity_endpoint assert cred._signer is None assert cred._token_uri is None - assert cred._service_account_email == "foo@example.com" + assert cred._service_account_email == "foo@project.iam.gserviceaccount.com" assert cred._target_audience == "audience" with pytest.raises(ValueError): cred.sign_bytes(b"bytes") @@ -1176,7 +1234,9 @@ def test_get_id_token_from_metadata( "google.auth.compute_engine._metadata.get_service_account_info", autospec=True ) def test_with_target_audience_for_metadata(self, get_service_account_info): - get_service_account_info.return_value = {"email": "foo@example.com"} + get_service_account_info.return_value = { + "email": "foo@project.iam.gserviceaccount.com" + } cred = credentials.IDTokenCredentials( mock.Mock(), "audience", use_metadata_identity_endpoint=True @@ -1187,13 +1247,15 @@ def test_with_target_audience_for_metadata(self, get_service_account_info): assert cred._use_metadata_identity_endpoint assert cred._signer is None assert cred._token_uri is None - assert cred._service_account_email == "foo@example.com" + assert cred._service_account_email == "foo@project.iam.gserviceaccount.com" @mock.patch( "google.auth.compute_engine._metadata.get_service_account_info", autospec=True ) def test_id_token_with_quota_project(self, get_service_account_info): - get_service_account_info.return_value = {"email": "foo@example.com"} + get_service_account_info.return_value = { + "email": "foo@project.iam.gserviceaccount.com" + } cred = credentials.IDTokenCredentials( mock.Mock(), "audience", use_metadata_identity_endpoint=True @@ -1204,7 +1266,7 @@ def test_id_token_with_quota_project(self, get_service_account_info): assert cred._use_metadata_identity_endpoint assert cred._signer is None assert cred._token_uri is None - assert cred._service_account_email == "foo@example.com" + assert cred._service_account_email == "foo@project.iam.gserviceaccount.com" @mock.patch( "google.auth.compute_engine._metadata.get_service_account_info", autospec=True @@ -1212,7 +1274,9 @@ def test_id_token_with_quota_project(self, get_service_account_info): @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_invalid_id_token_from_metadata(self, get, get_service_account_info): get.return_value = "invalid_id_token" - get_service_account_info.return_value = {"email": "foo@example.com"} + get_service_account_info.return_value = { + "email": "foo@project.iam.gserviceaccount.com" + } cred = credentials.IDTokenCredentials( mock.Mock(), "audience", use_metadata_identity_endpoint=True @@ -1227,7 +1291,9 @@ def test_invalid_id_token_from_metadata(self, get, get_service_account_info): @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_transport_error_from_metadata(self, get, get_service_account_info): get.side_effect = exceptions.TransportError("transport error") - get_service_account_info.return_value = {"email": "foo@example.com"} + get_service_account_info.return_value = { + "email": "foo@project.iam.gserviceaccount.com" + } cred = credentials.IDTokenCredentials( mock.Mock(), "audience", use_metadata_identity_endpoint=True @@ -1264,5 +1330,5 @@ def test_get_id_token_from_metadata_constructor(self): mock.Mock(), "audience", use_metadata_identity_endpoint=True, - service_account_email="foo@example.com", + service_account_email="foo@project.iam.gserviceaccount.com", ) diff --git a/packages/google-auth/tests/transport/aio/test_sessions.py b/packages/google-auth/tests/transport/aio/test_sessions.py index 58643c653ca2..de283b7b2e7f 100644 --- a/packages/google-auth/tests/transport/aio/test_sessions.py +++ b/packages/google-auth/tests/transport/aio/test_sessions.py @@ -335,6 +335,32 @@ async def test_http_delete_method_success(self): assert await response.read() == expected_payload response = await authed_session.close() + @pytest.mark.asyncio + async def test_configure_mtls_channel_with_custom_transport_and_broken_cert(self): + auth_request = MockRequest() + authed_session = sessions.AsyncAuthorizedSession( + self.credentials, auth_request=auth_request + ) + + with patch( + "google.auth.transport._mtls_helper.check_use_client_cert", + return_value=True, + ): + + def callback(): + return b"invalid-cert", b"invalid-key" + + with pytest.warns( + UserWarning, + match="Attempted to establish mTLS, but a custom async transport was provided", + ): + await authed_session.configure_mtls_channel(callback) + + assert authed_session._is_mtls is False + assert authed_session._cached_cert is None + + await authed_session.close() + def test_mock_request_clone(): request = MockRequest() diff --git a/packages/google-auth/tests/transport/aio/test_sessions_mtls.py b/packages/google-auth/tests/transport/aio/test_sessions_mtls.py index de9b056f27bb..b68766ca5b5d 100644 --- a/packages/google-auth/tests/transport/aio/test_sessions_mtls.py +++ b/packages/google-auth/tests/transport/aio/test_sessions_mtls.py @@ -193,9 +193,7 @@ async def test_configure_mtls_channel_custom_request(self): # If the request handler is not an AiohttpRequest, the library cannot configure # the connection to use mTLS, so _is_mtls must be False to reflect this unconfigured state. assert session._is_mtls is False - mock_make_context.assert_called_once_with( - b"fake_cert_data", b"fake_key_data" - ) + mock_make_context.assert_not_called() await session.close() @pytest.mark.asyncio diff --git a/packages/google-cloud-bigquery/tests/system/test_client.py b/packages/google-cloud-bigquery/tests/system/test_client.py index dc3f645dbce2..9ddec48428b1 100644 --- a/packages/google-cloud-bigquery/tests/system/test_client.py +++ b/packages/google-cloud-bigquery/tests/system/test_client.py @@ -2213,7 +2213,26 @@ def test_dbapi_connection_does_not_leak_sockets(self): break time.sleep(0.1) - self.assertLessEqual(conn_count_end, conn_count_start) + try: + self.assertLessEqual(conn_count_end, conn_count_start) + except AssertionError as e: + # Due to flakiness in this test (likely caused by OS cleanup delays or + # non-deterministic garbage collection of sockets), we want to capture + # the detailed state of connections in future failing runs to help + # decrease false positives and identify the root cause. + conn_debug = [ + f"Status: {c.status}, Laddr: {c.laddr}, Raddr: {c.raddr}" + for c in current_process.net_connections() + ] + debug_msg = "\n".join(conn_debug) + + raise AssertionError( + f"{e}\n\n" + f"--- Socket Leak Debug Info ---\n" + f"Start Count: {conn_count_start}\n" + f"End Count: {conn_count_end}\n" + f"Current Connections:\n{debug_msg}" + ) def _load_table_for_dml(self, rows, dataset_id, table_id): from google.cloud._testing import _NamedTemporaryFile diff --git a/packages/google-cloud-bigquery/tests/unit/job/test_async_job_retry.py b/packages/google-cloud-bigquery/tests/unit/job/test_async_job_retry.py index 4d9cccf932c3..f317651c5f45 100644 --- a/packages/google-cloud-bigquery/tests/unit/job/test_async_job_retry.py +++ b/packages/google-cloud-bigquery/tests/unit/job/test_async_job_retry.py @@ -15,11 +15,10 @@ from unittest import mock import google.api_core.retry +import google.cloud.bigquery.job from google.api_core import exceptions from . import helpers -import google.cloud.bigquery.job - PROJECT = "test-project" JOB_ID = "test-job-id" @@ -110,7 +109,7 @@ def test_result_w_retry_wo_state(global_time_lock): predicate=custom_predicate, initial=0.001, maximum=0.001, - deadline=0.1, + deadline=1.0, ) assert job.result(retry=custom_retry) is job diff --git a/packages/google-cloud-bigquery/tests/unit/job/test_query_job_retry.py b/packages/google-cloud-bigquery/tests/unit/job/test_query_job_retry.py index c8355b68832c..a4d3c5ec614c 100644 --- a/packages/google-cloud-bigquery/tests/unit/job/test_query_job_retry.py +++ b/packages/google-cloud-bigquery/tests/unit/job/test_query_job_retry.py @@ -11,14 +11,13 @@ # 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 concurrent.futures from unittest import mock -import concurrent.futures import freezegun -from google.api_core import exceptions import google.api_core.retry import pytest - +from google.api_core import exceptions from google.cloud.bigquery.client import _MIN_GET_QUERY_RESULTS_TIMEOUT from google.cloud.bigquery.job import QueryJob from google.cloud.bigquery.retry import DEFAULT_GET_JOB_TIMEOUT @@ -27,7 +26,6 @@ from ..helpers import make_connection from .helpers import _make_client - PROJECT = "test-project" JOB_ID = "test-job-id" QUERY = "select count(*) from persons" @@ -105,7 +103,7 @@ def test_result_w_custom_retry(global_time_lock): initial=0.001, maximum=0.001, multiplier=1.0, - deadline=0.1, + deadline=1.0, predicate=custom_predicate, ) diff --git a/packages/google-cloud-bigtable/noxfile.py b/packages/google-cloud-bigtable/noxfile.py index e6ef8c1e9911..a80ddfa5c3a3 100644 --- a/packages/google-cloud-bigtable/noxfile.py +++ b/packages/google-cloud-bigtable/noxfile.py @@ -33,6 +33,7 @@ "3.12", "3.13", "3.14", + "3.15", ] UNIT_TEST_STANDARD_DEPENDENCIES = [ @@ -66,6 +67,17 @@ SYSTEM_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = {} CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) + # 'docfx' is excluded since it only needs to run in 'docs-presubmit' nox.options.sessions = [ @@ -175,7 +187,9 @@ def mypy(session): "types-requests", ) session.install("google-cloud-testutils") - session.run("mypy", "-p", "google.cloud.bigtable.data") + session.run( + "mypy", f"--config-file={MYPY_CONFIG_FILE}", "-p", "google.cloud.bigtable.data" + ) @nox.session(python=DEFAULT_PYTHON_VERSION) @@ -216,25 +230,21 @@ def install_unittest_dependencies(session, *constraints): @nox.session(python=ALL_PYTHON) @nox.parametrize( "protobuf_implementation", - ["python", "upb", "cpp"], + ["python", "upb"], ) def unit(session, protobuf_implementation): # Install all test dependencies, then install this package in-place. - py_version = tuple([int(v) for v in session.python.split(".")]) - if protobuf_implementation == "cpp" and py_version >= (3, 11): - session.skip("cpp implementation is not supported in python 3.11+") + + # TODO(https://github.com/googleapis/google-cloud-python/issues/17741): + # Remove once `google-crc32c` wheels are published for 3.15 + if session.python == "3.15": + session.skip("Skipping 3.15 until wheels are available for google-crc32c.") constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" ) install_unittest_dependencies(session, "-c", constraints_path) - # TODO(https://github.com/googleapis/synthtool/issues/1976): - # Remove the 'cpp' implementation once support for Protobuf 3.x is dropped. - # The 'cpp' implementation requires Protobuf<4. - if protobuf_implementation == "cpp": - session.install("protobuf<4") - # Run py.test against the unit tests. session.run( "py.test", @@ -466,15 +476,11 @@ def docfx(session): @nox.session(python=DEFAULT_PYTHON_VERSION) @nox.parametrize( "protobuf_implementation", - ["python", "upb", "cpp"], + ["python", "upb"], ) def prerelease_deps(session, protobuf_implementation): """Run all tests with prerelease versions of dependencies installed.""" - py_version = tuple([int(v) for v in session.python.split(".")]) - if protobuf_implementation == "cpp" and py_version >= (3, 11): - session.skip("cpp implementation is not supported in python 3.11+") - # Install all dependencies session.install("-e", ".[all, tests, tracing]") unit_deps_all = UNIT_TEST_STANDARD_DEPENDENCIES + UNIT_TEST_EXTERNAL_DEPENDENCIES diff --git a/packages/google-cloud-commerceproducer/CHANGELOG.md b/packages/google-cloud-commerceproducer/CHANGELOG.md index 2e92b562cf25..2a13729576ad 100644 --- a/packages/google-cloud-commerceproducer/CHANGELOG.md +++ b/packages/google-cloud-commerceproducer/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.1.0 (2026-07-21) + + +### Features + +* **google/cloud/commerceproducer/v1beta:** add google-cloud-commerceproducer ([#17781](https://github.com/googleapis/google-cloud-python/issues/17781)) ([a12aeef](https://github.com/googleapis/google-cloud-python/commit/a12aeef53826672016f2d3bf08ffc9b9ce116af4)) + +## Changelog + [PyPI History][1] [1]: https://pypi.org/project/google-cloud-commerceproducer/#history diff --git a/packages/google-cloud-commerceproducer/google/cloud/commerceproducer/gapic_version.py b/packages/google-cloud-commerceproducer/google/cloud/commerceproducer/gapic_version.py index e89a0031d71b..075b8773ece3 100644 --- a/packages/google-cloud-commerceproducer/google/cloud/commerceproducer/gapic_version.py +++ b/packages/google-cloud-commerceproducer/google/cloud/commerceproducer/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "0.0.0" # {x-release-please-version} +__version__ = "0.1.0" # {x-release-please-version} diff --git a/packages/google-cloud-commerceproducer/google/cloud/commerceproducer_v1beta/gapic_version.py b/packages/google-cloud-commerceproducer/google/cloud/commerceproducer_v1beta/gapic_version.py index e89a0031d71b..075b8773ece3 100644 --- a/packages/google-cloud-commerceproducer/google/cloud/commerceproducer_v1beta/gapic_version.py +++ b/packages/google-cloud-commerceproducer/google/cloud/commerceproducer_v1beta/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "0.0.0" # {x-release-please-version} +__version__ = "0.1.0" # {x-release-please-version} diff --git a/packages/google-cloud-commerceproducer/samples/generated_samples/snippet_metadata_google.cloud.commerceproducer.v1beta.json b/packages/google-cloud-commerceproducer/samples/generated_samples/snippet_metadata_google.cloud.commerceproducer.v1beta.json index 1e731204a668..3d7ac05ea7e3 100644 --- a/packages/google-cloud-commerceproducer/samples/generated_samples/snippet_metadata_google.cloud.commerceproducer.v1beta.json +++ b/packages/google-cloud-commerceproducer/samples/generated_samples/snippet_metadata_google.cloud.commerceproducer.v1beta.json @@ -8,7 +8,7 @@ ], "language": "PYTHON", "name": "google-cloud-commerceproducer", - "version": "0.0.0" + "version": "0.1.0" }, "snippets": [ { diff --git a/packages/google-cloud-core/mypy.ini b/packages/google-cloud-core/mypy.ini deleted file mode 100644 index ed9dccd3b6fd..000000000000 --- a/packages/google-cloud-core/mypy.ini +++ /dev/null @@ -1,7 +0,0 @@ -[mypy] -python_version = 3.14 -namespace_packages = True -ignore_missing_imports = True - -[mypy-google.protobuf] -ignore_missing_imports = True diff --git a/packages/google-cloud-core/noxfile.py b/packages/google-cloud-core/noxfile.py index 2de40dfb9234..a86e71252c69 100644 --- a/packages/google-cloud-core/noxfile.py +++ b/packages/google-cloud-core/noxfile.py @@ -15,6 +15,7 @@ from __future__ import absolute_import import os +import pathlib import re import shutil @@ -32,7 +33,17 @@ "3.13", "3.14", ] -CURRENT_DIRECTORY = os.path.abspath(os.path.dirname(__file__)) +CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) # Error if a python version is missing nox.options.error_on_missing_interpreters = True @@ -62,7 +73,14 @@ def mypy(session): "types-mock", "types-protobuf!=4.24.0.20240106", ) - session.run("mypy", "-p", "google", "-p", "tests") + session.run( + "mypy", + f"--config-file={MYPY_CONFIG_FILE}", + "-p", + "google", + "-p", + "tests", + ) @nox.session(python=DEFAULT_PYTHON_VERSION) diff --git a/packages/google-cloud-dns/noxfile.py b/packages/google-cloud-dns/noxfile.py index bb38d1e08a91..55c042e6b68f 100644 --- a/packages/google-cloud-dns/noxfile.py +++ b/packages/google-cloud-dns/noxfile.py @@ -67,6 +67,17 @@ SYSTEM_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = {} CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) + nox.options.sessions = [ "unit-3.10", @@ -155,6 +166,7 @@ def mypy(session): session.install(".") session.run( "mypy", + f"--config-file={MYPY_CONFIG_FILE}", "-p", "google", ) diff --git a/packages/google-cloud-documentai-toolbox/noxfile.py b/packages/google-cloud-documentai-toolbox/noxfile.py index db70716c5d6b..d046a6f3d39b 100644 --- a/packages/google-cloud-documentai-toolbox/noxfile.py +++ b/packages/google-cloud-documentai-toolbox/noxfile.py @@ -69,6 +69,17 @@ SYSTEM_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = {} CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) + nox.options.sessions = [ "unit-3.10", @@ -498,6 +509,7 @@ def mypy(session): session.install("-e", ".") session.run( "mypy", + f"--config-file={MYPY_CONFIG_FILE}", "-p", "google.cloud.documentai_toolbox", "--check-untyped-defs", diff --git a/packages/google-cloud-firestore/noxfile.py b/packages/google-cloud-firestore/noxfile.py index 5ede8b454acf..95de020bc305 100644 --- a/packages/google-cloud-firestore/noxfile.py +++ b/packages/google-cloud-firestore/noxfile.py @@ -177,7 +177,6 @@ def mypy(session): f"--config-file={MYPY_CONFIG_FILE}", "-p", "google", - "--check-untyped-defs", *session.posargs, ) diff --git a/packages/google-cloud-ndb/noxfile.py b/packages/google-cloud-ndb/noxfile.py index e447badd711c..93564ffa28e0 100644 --- a/packages/google-cloud-ndb/noxfile.py +++ b/packages/google-cloud-ndb/noxfile.py @@ -31,6 +31,17 @@ DEFAULT_INTERPRETER = "3.14" ALL_INTERPRETERS = ("3.10", "3.11", "3.12", "3.13", "3.14") CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) + BLACK_VERSION = "black[jupyter]==23.7.0" RUFF_VERSION = "ruff==0.14.14" @@ -535,6 +546,7 @@ def mypy(session): session.install("-e", ".") session.run( "mypy", + f"--config-file={MYPY_CONFIG_FILE}", "-p", "google.cloud.ndb", "--check-untyped-defs", diff --git a/packages/google-cloud-productregistry/.coveragerc b/packages/google-cloud-productregistry/.coveragerc new file mode 100644 index 000000000000..25eb562d4b28 --- /dev/null +++ b/packages/google-cloud-productregistry/.coveragerc @@ -0,0 +1,13 @@ +[run] +branch = True + +[report] +show_missing = True +omit = + google/cloud/productregistry/__init__.py + google/cloud/productregistry/gapic_version.py +exclude_lines = + # Re-enable the standard pragma + pragma: NO COVER + # Ignore debug-only repr + def __repr__ diff --git a/packages/google-cloud-productregistry/.flake8 b/packages/google-cloud-productregistry/.flake8 new file mode 100644 index 000000000000..f9069a84687b --- /dev/null +++ b/packages/google-cloud-productregistry/.flake8 @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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. +# +[flake8] +# TODO(https://github.com/googleapis/gapic-generator-python/issues/2333): +# Resolve flake8 lint issues +ignore = E203, E231, E266, E501, W503 +exclude = + # TODO(https://github.com/googleapis/gapic-generator-python/issues/2333): + # Ensure that generated code passes flake8 lint + **/gapic/** + **/services/** + **/types/** + # Exclude Protobuf gencode + *_pb2.py + + # Standard linting exemptions. + **/.nox/** + __pycache__, + .git, + *.pyc, + conf.py diff --git a/packages/google-cloud-productregistry/.repo-metadata.json b/packages/google-cloud-productregistry/.repo-metadata.json new file mode 100644 index 000000000000..0a85446ca0a4 --- /dev/null +++ b/packages/google-cloud-productregistry/.repo-metadata.json @@ -0,0 +1,16 @@ +{ + "api_description": "cloudproductregistry.googleapis.com API.", + "api_id": "cloudproductregistry.googleapis.com", + "api_shortname": "cloudproductregistry", + "client_documentation": "https://cloud.google.com/python/docs/reference/google-cloud-productregistry/latest", + "default_version": "v1", + "distribution_name": "google-cloud-productregistry", + "issue_tracker": "https://issuetracker.google.com/issues/new?component=2173117", + "language": "python", + "library_type": "GAPIC_AUTO", + "name": "google-cloud-productregistry", + "name_pretty": "Cloud Product Registry", + "product_documentation": "https://docs.cloud.google.com/product-registry", + "release_level": "preview", + "repo": "googleapis/google-cloud-python" +} \ No newline at end of file diff --git a/packages/google-cloud-productregistry/CHANGELOG.md b/packages/google-cloud-productregistry/CHANGELOG.md new file mode 100644 index 000000000000..2a015ab4cf85 --- /dev/null +++ b/packages/google-cloud-productregistry/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +## 0.1.0 (2026-07-21) + + +### Features + +* **google/cloud/productregistry/v1:** add google-cloud-productregistry ([#17783](https://github.com/googleapis/google-cloud-python/issues/17783)) ([b7fa7df](https://github.com/googleapis/google-cloud-python/commit/b7fa7dfa369beda9f9236a3174035520b73c51a5)) + +## Changelog + +[PyPI History][1] + +[1]: https://pypi.org/project/google-cloud-productregistry/#history diff --git a/packages/google-cloud-productregistry/LICENSE b/packages/google-cloud-productregistry/LICENSE new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/packages/google-cloud-productregistry/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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-cloud-productregistry/MANIFEST.in b/packages/google-cloud-productregistry/MANIFEST.in new file mode 100644 index 000000000000..f932577add9d --- /dev/null +++ b/packages/google-cloud-productregistry/MANIFEST.in @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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. +# +include README.rst LICENSE +recursive-include google *.py *.pyi *.json *.proto py.typed +recursive-include tests * +global-exclude *.py[co] +global-exclude __pycache__ diff --git a/packages/google-cloud-productregistry/README.rst b/packages/google-cloud-productregistry/README.rst new file mode 100644 index 000000000000..2c143cc88009 --- /dev/null +++ b/packages/google-cloud-productregistry/README.rst @@ -0,0 +1,198 @@ +Python Client for Cloud Product Registry +======================================== + +|preview| |pypi| |versions| + +`Cloud Product Registry`_: cloudproductregistry.googleapis.com API. + +- `Client Library Documentation`_ +- `Product Documentation`_ + +.. |preview| image:: https://img.shields.io/badge/support-preview-orange.svg + :target: https://github.com/googleapis/google-cloud-python/blob/main/README.rst#stability-levels +.. |pypi| image:: https://img.shields.io/pypi/v/google-cloud-productregistry.svg + :target: https://pypi.org/project/google-cloud-productregistry/ +.. |versions| image:: https://img.shields.io/pypi/pyversions/google-cloud-productregistry.svg + :target: https://pypi.org/project/google-cloud-productregistry/ +.. _Cloud Product Registry: https://docs.cloud.google.com/product-registry +.. _Client Library Documentation: https://cloud.google.com/python/docs/reference/google-cloud-productregistry/latest/summary_overview +.. _Product Documentation: https://docs.cloud.google.com/product-registry + +Quick Start +----------- + +In order to use this library, you first need to go through the following steps: + +1. `Select or create a Cloud Platform project.`_ +2. `Enable billing for your project.`_ +3. `Enable the Cloud Product Registry.`_ +4. `Set up Authentication.`_ + +.. _Select or create a Cloud Platform project.: https://console.cloud.google.com/project +.. _Enable billing for your project.: https://cloud.google.com/billing/docs/how-to/modify-project#enable_billing_for_a_project +.. _Enable the Cloud Product Registry.: https://docs.cloud.google.com/product-registry +.. _Set up Authentication.: https://googleapis.dev/python/google-api-core/latest/auth.html + +Installation +~~~~~~~~~~~~ + +Install this library in a virtual environment using `venv`_. `venv`_ is a tool that +creates isolated Python environments. These isolated environments can have separate +versions of Python packages, which allows you to isolate one project's dependencies +from the dependencies of other projects. + +With `venv`_, it's possible to install this library without needing system +install permissions, and without clashing with the installed system +dependencies. + +.. _`venv`: https://docs.python.org/3/library/venv.html + + +Code samples and snippets +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Code samples and snippets live in the `samples/`_ folder. + +.. _samples/: https://github.com/googleapis/google-cloud-python/tree/main/packages/google-cloud-productregistry/samples + + +Supported Python Versions +^^^^^^^^^^^^^^^^^^^^^^^^^ +Our client libraries are compatible with all current `active`_ and `maintenance`_ versions of +Python. + +Python >= 3.10, including 3.14 + +.. _active: https://devguide.python.org/devcycle/#in-development-main-branch +.. _maintenance: https://devguide.python.org/devcycle/#maintenance-branches + +Unsupported Python Versions +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Python <= 3.9 + + +If you are using an `end-of-life`_ +version of Python, we recommend that you update as soon as possible to an actively supported version. + +.. _end-of-life: https://devguide.python.org/devcycle/#end-of-life-branches + +Mac/Linux +^^^^^^^^^ + +.. code-block:: console + + python3 -m venv + source /bin/activate + pip install google-cloud-productregistry + + +Windows +^^^^^^^ + +.. code-block:: console + + py -m venv + .\\Scripts\activate + pip install google-cloud-productregistry + +Next Steps +~~~~~~~~~~ + +- Read the `Client Library Documentation`_ for Cloud Product Registry + to see other available methods on the client. +- Read the `Cloud Product Registry Product documentation`_ to learn + more about the product and see How-to Guides. +- View this `README`_ to see the full list of Cloud + APIs that we cover. + +.. _Cloud Product Registry Product documentation: https://docs.cloud.google.com/product-registry +.. _README: https://github.com/googleapis/google-cloud-python/blob/main/README.rst + +Logging +------- + +This library uses the standard Python :code:`logging` functionality to log some RPC events that could be of interest for debugging and monitoring purposes. +Note the following: + +#. Logs may contain sensitive information. Take care to **restrict access to the logs** if they are saved, whether it be on local storage or on Google Cloud Logging. +#. Google may refine the occurrence, level, and content of various log messages in this library without flagging such changes as breaking. **Do not depend on immutability of the logging events**. +#. By default, the logging events from this library are not handled. You must **explicitly configure log handling** using one of the mechanisms below. + +Simple, environment-based configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To enable logging for this library without any changes in your code, set the :code:`GOOGLE_SDK_PYTHON_LOGGING_SCOPE` environment variable to a valid Google +logging scope. This configures handling of logging events (at level :code:`logging.DEBUG` or higher) from this library in a default manner, emitting the logged +messages in a structured format. It does not currently allow customizing the logging levels captured nor the handlers, formatters, etc. used for any logging +event. + +A logging scope is a period-separated namespace that begins with :code:`google`, identifying the Python module or package to log. + +- Valid logging scopes: :code:`google`, :code:`google.cloud.asset.v1`, :code:`google.api`, :code:`google.auth`, etc. +- Invalid logging scopes: :code:`foo`, :code:`123`, etc. + +**NOTE**: If the logging scope is invalid, the library does not set up any logging handlers. + +Environment-Based Examples +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Enabling the default handler for all Google-based loggers + +.. code-block:: console + + export GOOGLE_SDK_PYTHON_LOGGING_SCOPE=google + +- Enabling the default handler for a specific Google module (for a client library called :code:`library_v1`): + +.. code-block:: console + + export GOOGLE_SDK_PYTHON_LOGGING_SCOPE=google.cloud.library_v1 + + +Advanced, code-based configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also configure a valid logging scope using Python's standard `logging` mechanism. + +Code-Based Examples +^^^^^^^^^^^^^^^^^^^ + +- Configuring a handler for all Google-based loggers + +.. code-block:: python + + import logging + + from google.cloud import library_v1 + + base_logger = logging.getLogger("google") + base_logger.addHandler(logging.StreamHandler()) + base_logger.setLevel(logging.DEBUG) + +- Configuring a handler for a specific Google module (for a client library called :code:`library_v1`): + +.. code-block:: python + + import logging + + from google.cloud import library_v1 + + base_logger = logging.getLogger("google.cloud.library_v1") + base_logger.addHandler(logging.StreamHandler()) + base_logger.setLevel(logging.DEBUG) + +Logging details +~~~~~~~~~~~~~~~ + +#. Regardless of which of the mechanisms above you use to configure logging for this library, by default logging events are not propagated up to the root + logger from the `google`-level logger. If you need the events to be propagated to the root logger, you must explicitly set + :code:`logging.getLogger("google").propagate = True` in your code. +#. You can mix the different logging configurations above for different Google modules. For example, you may want use a code-based logging configuration for + one library, but decide you need to also set up environment-based logging configuration for another library. + + #. If you attempt to use both code-based and environment-based configuration for the same module, the environment-based configuration will be ineffectual + if the code -based configuration gets applied first. + +#. The Google-specific logging configurations (default handlers for environment-based configuration; not propagating logging events to the root logger) get + executed the first time *any* client library is instantiated in your application, and only if the affected loggers have not been previously configured. + (This is the reason for 2.i. above.) diff --git a/packages/google-cloud-productregistry/docs/CHANGELOG.md b/packages/google-cloud-productregistry/docs/CHANGELOG.md new file mode 120000 index 000000000000..04c99a55caae --- /dev/null +++ b/packages/google-cloud-productregistry/docs/CHANGELOG.md @@ -0,0 +1 @@ +../CHANGELOG.md \ No newline at end of file diff --git a/packages/google-cloud-productregistry/docs/README.rst b/packages/google-cloud-productregistry/docs/README.rst new file mode 100644 index 000000000000..2c143cc88009 --- /dev/null +++ b/packages/google-cloud-productregistry/docs/README.rst @@ -0,0 +1,198 @@ +Python Client for Cloud Product Registry +======================================== + +|preview| |pypi| |versions| + +`Cloud Product Registry`_: cloudproductregistry.googleapis.com API. + +- `Client Library Documentation`_ +- `Product Documentation`_ + +.. |preview| image:: https://img.shields.io/badge/support-preview-orange.svg + :target: https://github.com/googleapis/google-cloud-python/blob/main/README.rst#stability-levels +.. |pypi| image:: https://img.shields.io/pypi/v/google-cloud-productregistry.svg + :target: https://pypi.org/project/google-cloud-productregistry/ +.. |versions| image:: https://img.shields.io/pypi/pyversions/google-cloud-productregistry.svg + :target: https://pypi.org/project/google-cloud-productregistry/ +.. _Cloud Product Registry: https://docs.cloud.google.com/product-registry +.. _Client Library Documentation: https://cloud.google.com/python/docs/reference/google-cloud-productregistry/latest/summary_overview +.. _Product Documentation: https://docs.cloud.google.com/product-registry + +Quick Start +----------- + +In order to use this library, you first need to go through the following steps: + +1. `Select or create a Cloud Platform project.`_ +2. `Enable billing for your project.`_ +3. `Enable the Cloud Product Registry.`_ +4. `Set up Authentication.`_ + +.. _Select or create a Cloud Platform project.: https://console.cloud.google.com/project +.. _Enable billing for your project.: https://cloud.google.com/billing/docs/how-to/modify-project#enable_billing_for_a_project +.. _Enable the Cloud Product Registry.: https://docs.cloud.google.com/product-registry +.. _Set up Authentication.: https://googleapis.dev/python/google-api-core/latest/auth.html + +Installation +~~~~~~~~~~~~ + +Install this library in a virtual environment using `venv`_. `venv`_ is a tool that +creates isolated Python environments. These isolated environments can have separate +versions of Python packages, which allows you to isolate one project's dependencies +from the dependencies of other projects. + +With `venv`_, it's possible to install this library without needing system +install permissions, and without clashing with the installed system +dependencies. + +.. _`venv`: https://docs.python.org/3/library/venv.html + + +Code samples and snippets +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Code samples and snippets live in the `samples/`_ folder. + +.. _samples/: https://github.com/googleapis/google-cloud-python/tree/main/packages/google-cloud-productregistry/samples + + +Supported Python Versions +^^^^^^^^^^^^^^^^^^^^^^^^^ +Our client libraries are compatible with all current `active`_ and `maintenance`_ versions of +Python. + +Python >= 3.10, including 3.14 + +.. _active: https://devguide.python.org/devcycle/#in-development-main-branch +.. _maintenance: https://devguide.python.org/devcycle/#maintenance-branches + +Unsupported Python Versions +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Python <= 3.9 + + +If you are using an `end-of-life`_ +version of Python, we recommend that you update as soon as possible to an actively supported version. + +.. _end-of-life: https://devguide.python.org/devcycle/#end-of-life-branches + +Mac/Linux +^^^^^^^^^ + +.. code-block:: console + + python3 -m venv + source /bin/activate + pip install google-cloud-productregistry + + +Windows +^^^^^^^ + +.. code-block:: console + + py -m venv + .\\Scripts\activate + pip install google-cloud-productregistry + +Next Steps +~~~~~~~~~~ + +- Read the `Client Library Documentation`_ for Cloud Product Registry + to see other available methods on the client. +- Read the `Cloud Product Registry Product documentation`_ to learn + more about the product and see How-to Guides. +- View this `README`_ to see the full list of Cloud + APIs that we cover. + +.. _Cloud Product Registry Product documentation: https://docs.cloud.google.com/product-registry +.. _README: https://github.com/googleapis/google-cloud-python/blob/main/README.rst + +Logging +------- + +This library uses the standard Python :code:`logging` functionality to log some RPC events that could be of interest for debugging and monitoring purposes. +Note the following: + +#. Logs may contain sensitive information. Take care to **restrict access to the logs** if they are saved, whether it be on local storage or on Google Cloud Logging. +#. Google may refine the occurrence, level, and content of various log messages in this library without flagging such changes as breaking. **Do not depend on immutability of the logging events**. +#. By default, the logging events from this library are not handled. You must **explicitly configure log handling** using one of the mechanisms below. + +Simple, environment-based configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To enable logging for this library without any changes in your code, set the :code:`GOOGLE_SDK_PYTHON_LOGGING_SCOPE` environment variable to a valid Google +logging scope. This configures handling of logging events (at level :code:`logging.DEBUG` or higher) from this library in a default manner, emitting the logged +messages in a structured format. It does not currently allow customizing the logging levels captured nor the handlers, formatters, etc. used for any logging +event. + +A logging scope is a period-separated namespace that begins with :code:`google`, identifying the Python module or package to log. + +- Valid logging scopes: :code:`google`, :code:`google.cloud.asset.v1`, :code:`google.api`, :code:`google.auth`, etc. +- Invalid logging scopes: :code:`foo`, :code:`123`, etc. + +**NOTE**: If the logging scope is invalid, the library does not set up any logging handlers. + +Environment-Based Examples +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Enabling the default handler for all Google-based loggers + +.. code-block:: console + + export GOOGLE_SDK_PYTHON_LOGGING_SCOPE=google + +- Enabling the default handler for a specific Google module (for a client library called :code:`library_v1`): + +.. code-block:: console + + export GOOGLE_SDK_PYTHON_LOGGING_SCOPE=google.cloud.library_v1 + + +Advanced, code-based configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also configure a valid logging scope using Python's standard `logging` mechanism. + +Code-Based Examples +^^^^^^^^^^^^^^^^^^^ + +- Configuring a handler for all Google-based loggers + +.. code-block:: python + + import logging + + from google.cloud import library_v1 + + base_logger = logging.getLogger("google") + base_logger.addHandler(logging.StreamHandler()) + base_logger.setLevel(logging.DEBUG) + +- Configuring a handler for a specific Google module (for a client library called :code:`library_v1`): + +.. code-block:: python + + import logging + + from google.cloud import library_v1 + + base_logger = logging.getLogger("google.cloud.library_v1") + base_logger.addHandler(logging.StreamHandler()) + base_logger.setLevel(logging.DEBUG) + +Logging details +~~~~~~~~~~~~~~~ + +#. Regardless of which of the mechanisms above you use to configure logging for this library, by default logging events are not propagated up to the root + logger from the `google`-level logger. If you need the events to be propagated to the root logger, you must explicitly set + :code:`logging.getLogger("google").propagate = True` in your code. +#. You can mix the different logging configurations above for different Google modules. For example, you may want use a code-based logging configuration for + one library, but decide you need to also set up environment-based logging configuration for another library. + + #. If you attempt to use both code-based and environment-based configuration for the same module, the environment-based configuration will be ineffectual + if the code -based configuration gets applied first. + +#. The Google-specific logging configurations (default handlers for environment-based configuration; not propagating logging events to the root logger) get + executed the first time *any* client library is instantiated in your application, and only if the affected loggers have not been previously configured. + (This is the reason for 2.i. above.) diff --git a/packages/google-cloud-productregistry/docs/_static/custom.css b/packages/google-cloud-productregistry/docs/_static/custom.css new file mode 100644 index 000000000000..b0a295464b23 --- /dev/null +++ b/packages/google-cloud-productregistry/docs/_static/custom.css @@ -0,0 +1,20 @@ +div#python2-eol { + border-color: red; + border-width: medium; +} + +/* Ensure minimum width for 'Parameters' / 'Returns' column */ +dl.field-list > dt { + min-width: 100px +} + +/* Insert space between methods for readability */ +dl.method { + padding-top: 10px; + padding-bottom: 10px +} + +/* Insert empty space between classes */ +dl.class { + padding-bottom: 50px +} diff --git a/packages/google-cloud-productregistry/docs/_templates/layout.html b/packages/google-cloud-productregistry/docs/_templates/layout.html new file mode 100644 index 000000000000..95e9c77fcfe1 --- /dev/null +++ b/packages/google-cloud-productregistry/docs/_templates/layout.html @@ -0,0 +1,50 @@ + +{% extends "!layout.html" %} +{%- block content %} +{%- if theme_fixed_sidebar|lower == 'true' %} +
+ {{ sidebar() }} + {%- block document %} +
+ {%- if render_sidebar %} +
+ {%- endif %} + + {%- block relbar_top %} + {%- if theme_show_relbar_top|tobool %} + + {%- endif %} + {% endblock %} + +
+
+ As of January 1, 2020 this library no longer supports Python 2 on the latest released version. + Library versions released prior to that date will continue to be available. For more information please + visit Python 2 support on Google Cloud. +
+ {% block body %} {% endblock %} +
+ + {%- block relbar_bottom %} + {%- if theme_show_relbar_bottom|tobool %} + + {%- endif %} + {% endblock %} + + {%- if render_sidebar %} +
+ {%- endif %} +
+ {%- endblock %} +
+
+{%- else %} +{{ super() }} +{%- endif %} +{%- endblock %} diff --git a/packages/google-cloud-productregistry/docs/conf.py b/packages/google-cloud-productregistry/docs/conf.py new file mode 100644 index 000000000000..c8ec88eabfd3 --- /dev/null +++ b/packages/google-cloud-productregistry/docs/conf.py @@ -0,0 +1,417 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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. +## +# google-cloud-productregistry documentation build configuration file +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import logging +import os +import shlex +import sys +from typing import Any + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath("..")) + +# For plugins that can not read conf.py. +# See also: https://github.com/docascode/sphinx-docfx-yaml/issues/85 +sys.path.insert(0, os.path.abspath(".")) + +__version__ = "" + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +needs_sphinx = "4.5.0" + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.intersphinx", + "sphinx.ext.coverage", + "sphinx.ext.doctest", + "sphinx.ext.napoleon", + "sphinx.ext.todo", + "sphinx.ext.viewcode", + "recommonmark", +] + +# autodoc/autosummary flags +autoclass_content = "both" +autodoc_default_options = {"members": True} +autosummary_generate = True + + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = [".rst", ".md"] + +# The encoding of source files. +# source_encoding = 'utf-8-sig' + +# The root toctree document. +root_doc = "index" + +# General information about the project. +project = "google-cloud-productregistry" +copyright = "2026, Google, LLC" +author = "Google APIs" + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The full version, including alpha/beta/rc tags. +release = __version__ +# The short X.Y version. +version = ".".join(release.split(".")[0:2]) + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [ + "_build", + "**/.nox/**/*", + "samples/AUTHORING_GUIDE.md", + "samples/CONTRIBUTING.md", + "samples/snippets/README.rst", +] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = "alabaster" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +html_theme_options = { + "description": "Google Cloud Client Libraries for google-cloud-productregistry", + "github_user": "googleapis", + "github_repo": "google-cloud-python", + "github_banner": True, + "font_family": "'Roboto', Georgia, sans", + "head_font_family": "'Roboto', Georgia, serif", + "code_font_family": "'Roboto Mono', 'Consolas', monospace", +} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +# html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +# html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {} + +# If false, no module index is generated. +# html_domain_indices = True + +# If false, no index is generated. +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = "google-cloud-productregistry-doc" + +# -- Options for warnings ------------------------------------------------------ + + +suppress_warnings = [ + # Temporarily suppress this to avoid "more than one target found for + # cross-reference" warning, which are intractable for us to avoid while in + # a mono-repo. + # See https://github.com/sphinx-doc/sphinx/blob + # /2a65ffeef5c107c19084fabdd706cdff3f52d93c/sphinx/domains/python.py#L843 + "ref.python" +] + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', + # Latex figure (float) alignment + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ( + root_doc, + "google-cloud-productregistry.tex", + "google-cloud-productregistry Documentation", + author, + "manual", + ) +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ( + root_doc, + "google-cloud-productregistry", + "google-cloud-productregistry Documentation", + [author], + 1, + ) +] + +# If true, show URL addresses after external links. +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + root_doc, + "google-cloud-productregistry", + "google-cloud-productregistry Documentation", + author, + "google-cloud-productregistry", + "google-cloud-productregistry Library", + "APIs", + ) +] + +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# texinfo_no_detailmenu = False + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = { + "python": ("https://python.readthedocs.org/en/latest/", None), + "google-auth": ("https://googleapis.dev/python/google-auth/latest/", None), + "google.api_core": ( + "https://googleapis.dev/python/google-api-core/latest/", + None, + ), + "grpc": ("https://grpc.github.io/grpc/python/", None), + "proto-plus": ("https://proto-plus-python.readthedocs.io/en/latest/", None), + "protobuf": ("https://googleapis.dev/python/protobuf/latest/", None), +} + + +# Napoleon settings +napoleon_google_docstring = True +napoleon_numpy_docstring = True +napoleon_include_private_with_doc = False +napoleon_include_special_with_doc = True +napoleon_use_admonition_for_examples = False +napoleon_use_admonition_for_notes = False +napoleon_use_admonition_for_references = False +napoleon_use_ivar = False +napoleon_use_param = True +napoleon_use_rtype = True + + +# Setup for sphinx behaviors such as warning filters. +class UnexpectedUnindentFilter(logging.Filter): + """Filter out warnings about unexpected unindentation following bullet lists.""" + + def filter(self, record: logging.LogRecord) -> bool: + """Filter the log record. + + Args: + record (logging.LogRecord): The log record. + + Returns: + bool: False to suppress the warning, True to allow it. + """ + msg = record.getMessage() + if "Bullet list ends without a blank line" in msg: + return False + return True + + +def setup(app: Any) -> None: + """Setup the Sphinx application. + + Args: + app (Any): The Sphinx application. + """ + # Sphinx's logger is hierarchical. Adding a filter to the + # root 'sphinx' logger will catch warnings from all sub-loggers. + logger = logging.getLogger("sphinx") + logger.addFilter(UnexpectedUnindentFilter()) diff --git a/packages/google-cloud-productregistry/docs/index.rst b/packages/google-cloud-productregistry/docs/index.rst new file mode 100644 index 000000000000..4a60dc04c0a8 --- /dev/null +++ b/packages/google-cloud-productregistry/docs/index.rst @@ -0,0 +1,28 @@ +.. include:: README.rst + +.. include:: multiprocessing.rst + + +API Reference +------------- +.. toctree:: + :maxdepth: 2 + + productregistry_v1/services_ + productregistry_v1/types_ + + +Changelog +--------- + +For a list of all ``google-cloud-productregistry`` releases: + +.. toctree:: + :maxdepth: 2 + + CHANGELOG + +.. toctree:: + :hidden: + + summary_overview.md diff --git a/packages/google-cloud-productregistry/docs/multiprocessing.rst b/packages/google-cloud-productregistry/docs/multiprocessing.rst new file mode 100644 index 000000000000..536d17b2ea65 --- /dev/null +++ b/packages/google-cloud-productregistry/docs/multiprocessing.rst @@ -0,0 +1,7 @@ +.. note:: + + Because this client uses :mod:`grpc` library, it is safe to + share instances across threads. In multiprocessing scenarios, the best + practice is to create client instances *after* the invocation of + :func:`os.fork` by :class:`multiprocessing.pool.Pool` or + :class:`multiprocessing.Process`. diff --git a/packages/google-cloud-productregistry/docs/productregistry_v1/cloud_product_registry_read_service.rst b/packages/google-cloud-productregistry/docs/productregistry_v1/cloud_product_registry_read_service.rst new file mode 100644 index 000000000000..23f6c272ff25 --- /dev/null +++ b/packages/google-cloud-productregistry/docs/productregistry_v1/cloud_product_registry_read_service.rst @@ -0,0 +1,10 @@ +CloudProductRegistryReadService +------------------------------------------------- + +.. automodule:: google.cloud.productregistry_v1.services.cloud_product_registry_read_service + :members: + :inherited-members: + +.. automodule:: google.cloud.productregistry_v1.services.cloud_product_registry_read_service.pagers + :members: + :inherited-members: diff --git a/packages/google-cloud-productregistry/docs/productregistry_v1/services_.rst b/packages/google-cloud-productregistry/docs/productregistry_v1/services_.rst new file mode 100644 index 000000000000..75b79f871e38 --- /dev/null +++ b/packages/google-cloud-productregistry/docs/productregistry_v1/services_.rst @@ -0,0 +1,6 @@ +Services for Google Cloud Productregistry v1 API +================================================ +.. toctree:: + :maxdepth: 2 + + cloud_product_registry_read_service diff --git a/packages/google-cloud-productregistry/docs/productregistry_v1/types_.rst b/packages/google-cloud-productregistry/docs/productregistry_v1/types_.rst new file mode 100644 index 000000000000..b1dce2e1d082 --- /dev/null +++ b/packages/google-cloud-productregistry/docs/productregistry_v1/types_.rst @@ -0,0 +1,6 @@ +Types for Google Cloud Productregistry v1 API +============================================= + +.. automodule:: google.cloud.productregistry_v1.types + :members: + :show-inheritance: diff --git a/packages/google-cloud-productregistry/docs/summary_overview.md b/packages/google-cloud-productregistry/docs/summary_overview.md new file mode 100644 index 000000000000..a8fc6ca29adb --- /dev/null +++ b/packages/google-cloud-productregistry/docs/summary_overview.md @@ -0,0 +1,22 @@ +[ +This is a templated file. Adding content to this file may result in it being +reverted. Instead, if you want to place additional content, create an +"overview_content.md" file in `docs/` directory. The Sphinx tool will +pick up on the content and merge the content. +]: # + +# Cloud Product Registry API + +Overview of the APIs available for Cloud Product Registry API. + +## All entries + +Classes, methods and properties & attributes for +Cloud Product Registry API. + +[classes](https://cloud.google.com/python/docs/reference/google-cloud-productregistry/latest/summary_class.html) + +[methods](https://cloud.google.com/python/docs/reference/google-cloud-productregistry/latest/summary_method.html) + +[properties and +attributes](https://cloud.google.com/python/docs/reference/google-cloud-productregistry/latest/summary_property.html) diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry/__init__.py b/packages/google-cloud-productregistry/google/cloud/productregistry/__init__.py new file mode 100644 index 000000000000..23f413ac85e4 --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry/__init__.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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.cloud.productregistry import gapic_version as package_version + +__version__ = package_version.__version__ + + +from google.cloud.productregistry_v1.services.cloud_product_registry_read_service.async_client import ( + CloudProductRegistryReadServiceAsyncClient, +) +from google.cloud.productregistry_v1.services.cloud_product_registry_read_service.client import ( + CloudProductRegistryReadServiceClient, +) +from google.cloud.productregistry_v1.types.cloud_product_registry_read_service import ( + GetLogicalProductRequest, + GetLogicalProductVariantRequest, + GetProductSuiteRequest, + ListLogicalProductsRequest, + ListLogicalProductsResponse, + ListLogicalProductVariantsRequest, + ListLogicalProductVariantsResponse, + ListProductSuitesRequest, + ListProductSuitesResponse, + LookupEntityRequest, + LookupEntityResponse, +) +from google.cloud.productregistry_v1.types.lifecycle_state import LifecycleState +from google.cloud.productregistry_v1.types.logical_product import LogicalProduct +from google.cloud.productregistry_v1.types.logical_product_variant import ( + LogicalProductVariant, +) +from google.cloud.productregistry_v1.types.product_suite import ProductSuite + +__all__ = ( + "CloudProductRegistryReadServiceClient", + "CloudProductRegistryReadServiceAsyncClient", + "GetLogicalProductRequest", + "GetLogicalProductVariantRequest", + "GetProductSuiteRequest", + "ListLogicalProductsRequest", + "ListLogicalProductsResponse", + "ListLogicalProductVariantsRequest", + "ListLogicalProductVariantsResponse", + "ListProductSuitesRequest", + "ListProductSuitesResponse", + "LookupEntityRequest", + "LookupEntityResponse", + "LifecycleState", + "LogicalProduct", + "LogicalProductVariant", + "ProductSuite", +) diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry/gapic_version.py b/packages/google-cloud-productregistry/google/cloud/productregistry/gapic_version.py new file mode 100644 index 000000000000..075b8773ece3 --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry/gapic_version.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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.1.0" # {x-release-please-version} diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry/py.typed b/packages/google-cloud-productregistry/google/cloud/productregistry/py.typed new file mode 100644 index 000000000000..d7d247bb5e20 --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry/py.typed @@ -0,0 +1,2 @@ +# Marker file for PEP 561. +# The google-cloud-productregistry package uses inline types. diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/__init__.py b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/__init__.py new file mode 100644 index 000000000000..326398019c70 --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/__init__.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 sys + +import google.api_core as api_core + +from google.cloud.productregistry_v1 import gapic_version as package_version + +__version__ = package_version.__version__ + +from importlib import metadata + +from .services.cloud_product_registry_read_service import ( + CloudProductRegistryReadServiceAsyncClient, + CloudProductRegistryReadServiceClient, +) +from .types.cloud_product_registry_read_service import ( + GetLogicalProductRequest, + GetLogicalProductVariantRequest, + GetProductSuiteRequest, + ListLogicalProductsRequest, + ListLogicalProductsResponse, + ListLogicalProductVariantsRequest, + ListLogicalProductVariantsResponse, + ListProductSuitesRequest, + ListProductSuitesResponse, + LookupEntityRequest, + LookupEntityResponse, +) +from .types.lifecycle_state import LifecycleState +from .types.logical_product import LogicalProduct +from .types.logical_product_variant import LogicalProductVariant +from .types.product_suite import ProductSuite + +if hasattr(api_core, "check_python_version") and hasattr( + api_core, "check_dependency_versions" +): # pragma: NO COVER + api_core.check_python_version("google.cloud.productregistry_v1") # type: ignore + api_core.check_dependency_versions("google.cloud.productregistry_v1") # type: ignore +else: # pragma: NO COVER + # An older version of api_core is installed which does not define the + # functions above. We do equivalent checks manually. + try: + import warnings + + _py_version_str = sys.version.split()[0] + _package_label = "google.cloud.productregistry_v1" + if sys.version_info < (3, 10): + warnings.warn( + "You are using a non-supported Python version " + + f"({_py_version_str}). Google will not post any further " + + f"updates to {_package_label} supporting this Python version. " + + "Please upgrade to the latest Python version, or at " + + f"least to Python 3.10, and then update {_package_label}.", + FutureWarning, + ) + + def parse_version_to_tuple(version_string: str): + """Safely converts a semantic version string to a comparable tuple of integers. + Example: "6.33.5" -> (6, 33, 5) + Ignores non-numeric parts and handles common version formats. + Args: + version_string: Version string in the format "x.y.z" or "x.y.z" + Returns: + Tuple of integers for the parsed version string. + """ + parts = [] + for part in version_string.split("."): + try: + parts.append(int(part)) + except ValueError: + # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here. + # This is a simplification compared to 'packaging.parse_version', but sufficient + # for comparing strictly numeric semantic versions. + break + return tuple(parts) + + def _get_version(dependency_name): + try: + version_string: str = metadata.version(dependency_name) + parsed_version = parse_version_to_tuple(version_string) + return (parsed_version, version_string) + except Exception: + # Catch exceptions from metadata.version() (e.g., PackageNotFoundError) + # or errors during parse_version_to_tuple + return (None, "--") + + _dependency_package = "google.protobuf" + _next_supported_version = "6.33.5" + _next_supported_version_tuple = (6, 33, 5) + _recommendation = " (we recommend 7.x)" + (_version_used, _version_used_string) = _get_version(_dependency_package) + if _version_used and _version_used < _next_supported_version_tuple: + warnings.warn( + f"Package {_package_label} depends on " + + f"{_dependency_package}, currently installed at version " + + f"{_version_used_string}. Future updates to " + + f"{_package_label} will require {_dependency_package} at " + + f"version {_next_supported_version} or higher{_recommendation}." + + " Please ensure " + + "that either (a) your Python environment doesn't pin the " + + f"version of {_dependency_package}, so that updates to " + + f"{_package_label} can require the higher version, or " + + "(b) you manually update your Python environment to use at " + + f"least version {_next_supported_version} of " + + f"{_dependency_package}.", + FutureWarning, + ) + except Exception: + warnings.warn( + "Could not determine the version of Python " + + "currently being used. To continue receiving " + + "updates for {_package_label}, ensure you are " + + "using a supported version of Python; see " + + "https://devguide.python.org/versions/" + ) + +__all__ = ( + "CloudProductRegistryReadServiceAsyncClient", + "CloudProductRegistryReadServiceClient", + "GetLogicalProductRequest", + "GetLogicalProductVariantRequest", + "GetProductSuiteRequest", + "LifecycleState", + "ListLogicalProductVariantsRequest", + "ListLogicalProductVariantsResponse", + "ListLogicalProductsRequest", + "ListLogicalProductsResponse", + "ListProductSuitesRequest", + "ListProductSuitesResponse", + "LogicalProduct", + "LogicalProductVariant", + "LookupEntityRequest", + "LookupEntityResponse", + "ProductSuite", +) diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/gapic_metadata.json b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/gapic_metadata.json new file mode 100644 index 000000000000..e7b32cb1bbfb --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/gapic_metadata.json @@ -0,0 +1,133 @@ + { + "comment": "This file maps proto services/RPCs to the corresponding library clients/methods", + "language": "python", + "libraryPackage": "google.cloud.productregistry_v1", + "protoPackage": "google.cloud.productregistry.v1", + "schema": "1.0", + "services": { + "CloudProductRegistryReadService": { + "clients": { + "grpc": { + "libraryClient": "CloudProductRegistryReadServiceClient", + "rpcs": { + "GetLogicalProduct": { + "methods": [ + "get_logical_product" + ] + }, + "GetLogicalProductVariant": { + "methods": [ + "get_logical_product_variant" + ] + }, + "GetProductSuite": { + "methods": [ + "get_product_suite" + ] + }, + "ListLogicalProductVariants": { + "methods": [ + "list_logical_product_variants" + ] + }, + "ListLogicalProducts": { + "methods": [ + "list_logical_products" + ] + }, + "ListProductSuites": { + "methods": [ + "list_product_suites" + ] + }, + "LookupEntity": { + "methods": [ + "lookup_entity" + ] + } + } + }, + "grpc-async": { + "libraryClient": "CloudProductRegistryReadServiceAsyncClient", + "rpcs": { + "GetLogicalProduct": { + "methods": [ + "get_logical_product" + ] + }, + "GetLogicalProductVariant": { + "methods": [ + "get_logical_product_variant" + ] + }, + "GetProductSuite": { + "methods": [ + "get_product_suite" + ] + }, + "ListLogicalProductVariants": { + "methods": [ + "list_logical_product_variants" + ] + }, + "ListLogicalProducts": { + "methods": [ + "list_logical_products" + ] + }, + "ListProductSuites": { + "methods": [ + "list_product_suites" + ] + }, + "LookupEntity": { + "methods": [ + "lookup_entity" + ] + } + } + }, + "rest": { + "libraryClient": "CloudProductRegistryReadServiceClient", + "rpcs": { + "GetLogicalProduct": { + "methods": [ + "get_logical_product" + ] + }, + "GetLogicalProductVariant": { + "methods": [ + "get_logical_product_variant" + ] + }, + "GetProductSuite": { + "methods": [ + "get_product_suite" + ] + }, + "ListLogicalProductVariants": { + "methods": [ + "list_logical_product_variants" + ] + }, + "ListLogicalProducts": { + "methods": [ + "list_logical_products" + ] + }, + "ListProductSuites": { + "methods": [ + "list_product_suites" + ] + }, + "LookupEntity": { + "methods": [ + "lookup_entity" + ] + } + } + } + } + } + } +} diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/gapic_version.py b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/gapic_version.py new file mode 100644 index 000000000000..075b8773ece3 --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/gapic_version.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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.1.0" # {x-release-please-version} diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/py.typed b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/py.typed new file mode 100644 index 000000000000..d7d247bb5e20 --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/py.typed @@ -0,0 +1,2 @@ +# Marker file for PEP 561. +# The google-cloud-productregistry package uses inline types. diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/__init__.py b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/__init__.py new file mode 100644 index 000000000000..32b36c5c4fe0 --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/__init__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/__init__.py b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/__init__.py new file mode 100644 index 000000000000..23f492a099a9 --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 CloudProductRegistryReadServiceAsyncClient +from .client import CloudProductRegistryReadServiceClient + +__all__ = ( + "CloudProductRegistryReadServiceClient", + "CloudProductRegistryReadServiceAsyncClient", +) diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/async_client.py b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/async_client.py new file mode 100644 index 000000000000..493277e83b86 --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/async_client.py @@ -0,0 +1,1146 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 logging as std_logging +import re +from collections import OrderedDict +from typing import ( + Callable, + Dict, + Mapping, + MutableMapping, + MutableSequence, + Optional, + Sequence, + Tuple, + Type, + Union, +) + +import google.protobuf +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 + +from google.cloud.productregistry_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.cloud.productregistry_v1.services.cloud_product_registry_read_service import ( + pagers, +) +from google.cloud.productregistry_v1.types import ( + cloud_product_registry_read_service, + lifecycle_state, + logical_product, + logical_product_variant, + product_suite, +) + +from .client import CloudProductRegistryReadServiceClient +from .transports.base import ( + DEFAULT_CLIENT_INFO, + CloudProductRegistryReadServiceTransport, +) +from .transports.grpc_asyncio import CloudProductRegistryReadServiceGrpcAsyncIOTransport + +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 CloudProductRegistryReadServiceAsyncClient: + """Cloud Product Registry Read Service provides capabilities to + access all first and third party Google Cloud products. + """ + + _client: CloudProductRegistryReadServiceClient + + # Copy defaults from the synchronous client for use here. + # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead. + DEFAULT_ENDPOINT = CloudProductRegistryReadServiceClient.DEFAULT_ENDPOINT + DEFAULT_MTLS_ENDPOINT = CloudProductRegistryReadServiceClient.DEFAULT_MTLS_ENDPOINT + _DEFAULT_ENDPOINT_TEMPLATE = ( + CloudProductRegistryReadServiceClient._DEFAULT_ENDPOINT_TEMPLATE + ) + _DEFAULT_UNIVERSE = CloudProductRegistryReadServiceClient._DEFAULT_UNIVERSE + + logical_product_path = staticmethod( + CloudProductRegistryReadServiceClient.logical_product_path + ) + parse_logical_product_path = staticmethod( + CloudProductRegistryReadServiceClient.parse_logical_product_path + ) + logical_product_variant_path = staticmethod( + CloudProductRegistryReadServiceClient.logical_product_variant_path + ) + parse_logical_product_variant_path = staticmethod( + CloudProductRegistryReadServiceClient.parse_logical_product_variant_path + ) + product_suite_path = staticmethod( + CloudProductRegistryReadServiceClient.product_suite_path + ) + parse_product_suite_path = staticmethod( + CloudProductRegistryReadServiceClient.parse_product_suite_path + ) + common_billing_account_path = staticmethod( + CloudProductRegistryReadServiceClient.common_billing_account_path + ) + parse_common_billing_account_path = staticmethod( + CloudProductRegistryReadServiceClient.parse_common_billing_account_path + ) + common_folder_path = staticmethod( + CloudProductRegistryReadServiceClient.common_folder_path + ) + parse_common_folder_path = staticmethod( + CloudProductRegistryReadServiceClient.parse_common_folder_path + ) + common_organization_path = staticmethod( + CloudProductRegistryReadServiceClient.common_organization_path + ) + parse_common_organization_path = staticmethod( + CloudProductRegistryReadServiceClient.parse_common_organization_path + ) + common_project_path = staticmethod( + CloudProductRegistryReadServiceClient.common_project_path + ) + parse_common_project_path = staticmethod( + CloudProductRegistryReadServiceClient.parse_common_project_path + ) + common_location_path = staticmethod( + CloudProductRegistryReadServiceClient.common_location_path + ) + parse_common_location_path = staticmethod( + CloudProductRegistryReadServiceClient.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: + CloudProductRegistryReadServiceAsyncClient: The constructed client. + """ + sa_info_func = ( + CloudProductRegistryReadServiceClient.from_service_account_info.__func__ # type: ignore + ) + return sa_info_func( + CloudProductRegistryReadServiceAsyncClient, info, *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: + CloudProductRegistryReadServiceAsyncClient: The constructed client. + """ + sa_file_func = ( + CloudProductRegistryReadServiceClient.from_service_account_file.__func__ # type: ignore + ) + return sa_file_func( + CloudProductRegistryReadServiceAsyncClient, filename, *args, **kwargs + ) + + from_service_account_json = from_service_account_file + + @classmethod + def get_mtls_endpoint_and_cert_source( + cls, client_options: Optional[ClientOptions] = None + ): + """Return the API endpoint and client cert source for mutual TLS. + + The client cert source is determined in the following order: + (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the + client cert source is None. + (2) if `client_options.client_cert_source` is provided, use the provided one; if the + default client cert source exists, use the default one; otherwise the client cert + source is None. + + The API endpoint is determined in the following order: + (1) if `client_options.api_endpoint` if provided, use the provided one. + (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the + default mTLS endpoint; if the environment 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 CloudProductRegistryReadServiceClient.get_mtls_endpoint_and_cert_source( + client_options + ) # type: ignore + + @property + def transport(self) -> CloudProductRegistryReadServiceTransport: + """Returns the transport used by the client instance. + + Returns: + CloudProductRegistryReadServiceTransport: The transport used by the client instance. + """ + return self._client.transport + + @property + def api_endpoint(self) -> str: + """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 = CloudProductRegistryReadServiceClient.get_transport_class + + def __init__( + self, + *, + credentials: Optional[ga_credentials.Credentials] = None, + transport: Optional[ + Union[ + str, + CloudProductRegistryReadServiceTransport, + Callable[..., CloudProductRegistryReadServiceTransport], + ] + ] = "grpc_asyncio", + client_options: Optional[ClientOptions] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiates the cloud product registry read 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,CloudProductRegistryReadServiceTransport,Callable[..., CloudProductRegistryReadServiceTransport]]]): + 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 CloudProductRegistryReadServiceTransport 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 = CloudProductRegistryReadServiceClient( + 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.cloud.productregistry_v1.CloudProductRegistryReadServiceAsyncClient`.", + extra={ + "serviceName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "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.cloud.productregistry.v1.CloudProductRegistryReadService", + "credentialsType": None, + }, + ) + + async def get_product_suite( + self, + request: Optional[ + Union[cloud_product_registry_read_service.GetProductSuiteRequest, 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]]] = (), + ) -> product_suite.ProductSuite: + r"""Get details of a ProductSuite. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import productregistry_v1 + + async def sample_get_product_suite(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceAsyncClient() + + # Initialize request argument(s) + request = productregistry_v1.GetProductSuiteRequest( + name="name_value", + ) + + # Make the request + response = await client.get_product_suite(request=request) + + # Handle the response + print(response) + + Args: + request (Optional[Union[google.cloud.productregistry_v1.types.GetProductSuiteRequest, dict]]): + The request object. Request message for GetProductSuite. + name (:class:`str`): + Required. The name of the ProductSuite to retrieve. + Format: productSuites/{product_suite} + + 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.cloud.productregistry_v1.types.ProductSuite: + Represents a unified grouping of + products sharing a common brand and + market positioning. + + """ + # 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, cloud_product_registry_read_service.GetProductSuiteRequest + ): + request = cloud_product_registry_read_service.GetProductSuiteRequest( + 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_suite + ] + + # 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_product_suites( + self, + request: Optional[ + Union[cloud_product_registry_read_service.ListProductSuitesRequest, dict] + ] = None, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> pagers.ListProductSuitesAsyncPager: + r"""Lists ProductSuites. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import productregistry_v1 + + async def sample_list_product_suites(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceAsyncClient() + + # Initialize request argument(s) + request = productregistry_v1.ListProductSuitesRequest( + ) + + # Make the request + page_result = client.list_product_suites(request=request) + + # Handle the response + async for response in page_result: + print(response) + + Args: + request (Optional[Union[google.cloud.productregistry_v1.types.ListProductSuitesRequest, dict]]): + The request object. Request message for + ListProductSuites. + 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.cloud.productregistry_v1.services.cloud_product_registry_read_service.pagers.ListProductSuitesAsyncPager: + Response message for + ListProductSuites. + Iterating over this object will yield + results and resolve additional pages + automatically. + + """ + # 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, cloud_product_registry_read_service.ListProductSuitesRequest + ): + request = cloud_product_registry_read_service.ListProductSuitesRequest( + request + ) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._client._transport._wrapped_methods[ + self._client._transport.list_product_suites + ] + + # 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.ListProductSuitesAsyncPager( + method=rpc, + request=request, + response=response, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Done; return the response. + return response + + async def get_logical_product( + self, + request: Optional[ + Union[cloud_product_registry_read_service.GetLogicalProductRequest, 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]]] = (), + ) -> logical_product.LogicalProduct: + r"""Gets details of a LogicalProduct. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import productregistry_v1 + + async def sample_get_logical_product(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceAsyncClient() + + # Initialize request argument(s) + request = productregistry_v1.GetLogicalProductRequest( + name="name_value", + ) + + # Make the request + response = await client.get_logical_product(request=request) + + # Handle the response + print(response) + + Args: + request (Optional[Union[google.cloud.productregistry_v1.types.GetLogicalProductRequest, dict]]): + The request object. Request message for + GetLogicalProduct. + name (:class:`str`): + Required. The name of the LogicalProduct to retrieve. + Format: logicalProducts/{logical_product} + + 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.cloud.productregistry_v1.types.LogicalProduct: + Represents an independent service + offering that can be provisioned by a + customer. + + """ + # 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, cloud_product_registry_read_service.GetLogicalProductRequest + ): + request = cloud_product_registry_read_service.GetLogicalProductRequest( + 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_logical_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_logical_products( + self, + request: Optional[ + Union[cloud_product_registry_read_service.ListLogicalProductsRequest, dict] + ] = None, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> pagers.ListLogicalProductsAsyncPager: + r"""Lists LogicalProducts matching given criteria. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import productregistry_v1 + + async def sample_list_logical_products(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceAsyncClient() + + # Initialize request argument(s) + request = productregistry_v1.ListLogicalProductsRequest( + ) + + # Make the request + page_result = client.list_logical_products(request=request) + + # Handle the response + async for response in page_result: + print(response) + + Args: + request (Optional[Union[google.cloud.productregistry_v1.types.ListLogicalProductsRequest, dict]]): + The request object. Request message for + ListLogicalProducts. + 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.cloud.productregistry_v1.services.cloud_product_registry_read_service.pagers.ListLogicalProductsAsyncPager: + Response message for + ListLogicalProducts. + Iterating over this object will yield + results and resolve additional pages + automatically. + + """ + # 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, cloud_product_registry_read_service.ListLogicalProductsRequest + ): + request = cloud_product_registry_read_service.ListLogicalProductsRequest( + request + ) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._client._transport._wrapped_methods[ + self._client._transport.list_logical_products + ] + + # 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.ListLogicalProductsAsyncPager( + method=rpc, + request=request, + response=response, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Done; return the response. + return response + + async def get_logical_product_variant( + self, + request: Optional[ + Union[ + cloud_product_registry_read_service.GetLogicalProductVariantRequest, + 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]]] = (), + ) -> logical_product_variant.LogicalProductVariant: + r"""Get details of a LogicalProductVariant. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import productregistry_v1 + + async def sample_get_logical_product_variant(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceAsyncClient() + + # Initialize request argument(s) + request = productregistry_v1.GetLogicalProductVariantRequest( + name="name_value", + ) + + # Make the request + response = await client.get_logical_product_variant(request=request) + + # Handle the response + print(response) + + Args: + request (Optional[Union[google.cloud.productregistry_v1.types.GetLogicalProductVariantRequest, dict]]): + The request object. Request message for + GetLogicalProductVariant. + name (:class:`str`): + Required. The name of the LogicalProductVariant to + retrieve. Format: + logicalProducts/{logical_product}/variants/{variant} + + 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.cloud.productregistry_v1.types.LogicalProductVariant: + Represents a distinct offering + derived from a primary product that + retains core functionalities but offers + specialized features for a specific + market segment. + + """ + # 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, cloud_product_registry_read_service.GetLogicalProductVariantRequest + ): + request = ( + cloud_product_registry_read_service.GetLogicalProductVariantRequest( + 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_logical_product_variant + ] + + # 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_logical_product_variants( + self, + request: Optional[ + Union[ + cloud_product_registry_read_service.ListLogicalProductVariantsRequest, + 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.ListLogicalProductVariantsAsyncPager: + r"""Lists LogicalProductVariants matching given criteria. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import productregistry_v1 + + async def sample_list_logical_product_variants(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceAsyncClient() + + # Initialize request argument(s) + request = productregistry_v1.ListLogicalProductVariantsRequest( + parent="parent_value", + ) + + # Make the request + page_result = client.list_logical_product_variants(request=request) + + # Handle the response + async for response in page_result: + print(response) + + Args: + request (Optional[Union[google.cloud.productregistry_v1.types.ListLogicalProductVariantsRequest, dict]]): + The request object. Request message for + ListLogicalProductVariants. + parent (:class:`str`): + Required. Parent logical product id. Format: + logicalProducts/{logical_product} + + 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.cloud.productregistry_v1.services.cloud_product_registry_read_service.pagers.ListLogicalProductVariantsAsyncPager: + Response message for + ListLogicalProductVariants. + 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, + cloud_product_registry_read_service.ListLogicalProductVariantsRequest, + ): + request = ( + cloud_product_registry_read_service.ListLogicalProductVariantsRequest( + 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_logical_product_variants + ] + + # 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.ListLogicalProductVariantsAsyncPager( + method=rpc, + request=request, + response=response, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Done; return the response. + return response + + async def lookup_entity( + self, + request: Optional[ + Union[cloud_product_registry_read_service.LookupEntityRequest, dict] + ] = None, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> cloud_product_registry_read_service.LookupEntityResponse: + r"""Look up entities. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import productregistry_v1 + + async def sample_lookup_entity(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceAsyncClient() + + # Initialize request argument(s) + request = productregistry_v1.LookupEntityRequest( + lookup_uri="lookup_uri_value", + ) + + # Make the request + response = await client.lookup_entity(request=request) + + # Handle the response + print(response) + + Args: + request (Optional[Union[google.cloud.productregistry_v1.types.LookupEntityRequest, dict]]): + The request object. Request message for LookupEntity. + 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.cloud.productregistry_v1.types.LookupEntityResponse: + Response message for LookupEntity. + """ + # 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, cloud_product_registry_read_service.LookupEntityRequest + ): + request = cloud_product_registry_read_service.LookupEntityRequest(request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._client._transport._wrapped_methods[ + self._client._transport.lookup_entity + ] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata( + (("lookup_uri", request.lookup_uri),) + ), + ) + + # 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 __aenter__(self) -> "CloudProductRegistryReadServiceAsyncClient": + 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__ = ("CloudProductRegistryReadServiceAsyncClient",) diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/client.py b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/client.py new file mode 100644 index 000000000000..6c84cbd7406b --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/client.py @@ -0,0 +1,1589 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 os +import re +import warnings +from collections import OrderedDict +from http import HTTPStatus +from typing import ( + Callable, + Dict, + Mapping, + MutableMapping, + MutableSequence, + Optional, + Sequence, + Tuple, + Type, + Union, + cast, +) + +import google.protobuf +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 + +from google.cloud.productregistry_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.cloud.productregistry_v1.services.cloud_product_registry_read_service import ( + pagers, +) +from google.cloud.productregistry_v1.types import ( + cloud_product_registry_read_service, + lifecycle_state, + logical_product, + logical_product_variant, + product_suite, +) + +from .transports.base import ( + DEFAULT_CLIENT_INFO, + CloudProductRegistryReadServiceTransport, +) +from .transports.grpc import CloudProductRegistryReadServiceGrpcTransport +from .transports.grpc_asyncio import CloudProductRegistryReadServiceGrpcAsyncIOTransport +from .transports.rest import CloudProductRegistryReadServiceRestTransport + + +class CloudProductRegistryReadServiceClientMeta(type): + """Metaclass for the CloudProductRegistryReadService 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[CloudProductRegistryReadServiceTransport]] + _transport_registry["grpc"] = CloudProductRegistryReadServiceGrpcTransport + _transport_registry["grpc_asyncio"] = ( + CloudProductRegistryReadServiceGrpcAsyncIOTransport + ) + _transport_registry["rest"] = CloudProductRegistryReadServiceRestTransport + + def get_transport_class( + cls, + label: Optional[str] = None, + ) -> Type[CloudProductRegistryReadServiceTransport]: + """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 CloudProductRegistryReadServiceClient( + metaclass=CloudProductRegistryReadServiceClientMeta +): + """Cloud Product Registry Read Service provides capabilities to + access all first and third party Google Cloud products. + """ + + @staticmethod + def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]: + """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: + Optional[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) + if m is None: + # Could not parse api_endpoint; return as-is. + return 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 = "cloudproductregistry.googleapis.com" + DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__( # type: ignore + DEFAULT_ENDPOINT + ) + + _DEFAULT_ENDPOINT_TEMPLATE = "cloudproductregistry.{UNIVERSE_DOMAIN}" + _DEFAULT_UNIVERSE = "googleapis.com" + + @staticmethod + def _use_client_cert_effective(): + """Returns whether client certificate should be used for mTLS if the + google-auth version supports should_use_client_cert automatic mTLS enablement. + + Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. + + Returns: + bool: whether client certificate should be used for mTLS + Raises: + ValueError: (If using a version of google-auth without should_use_client_cert and + GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.) + """ + # check if google-auth version supports should_use_client_cert for automatic mTLS enablement + if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER + return mtls.should_use_client_cert() + else: # pragma: NO COVER + # if unsupported, fallback to reading from env var + use_client_cert_str = os.getenv( + "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false" + ).lower() + if use_client_cert_str not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" + " either `true` or `false`" + ) + return use_client_cert_str == "true" + + @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: + CloudProductRegistryReadServiceClient: 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: + CloudProductRegistryReadServiceClient: 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) -> CloudProductRegistryReadServiceTransport: + """Returns the transport used by the client instance. + + Returns: + CloudProductRegistryReadServiceTransport: The transport used by the client + instance. + """ + return self._transport + + @staticmethod + def logical_product_path( + logical_product: str, + ) -> str: + """Returns a fully-qualified logical_product string.""" + return "logicalProducts/{logical_product}".format( + logical_product=logical_product, + ) + + @staticmethod + def parse_logical_product_path(path: str) -> Dict[str, str]: + """Parses a logical_product path into its component segments.""" + m = re.match(r"^logicalProducts/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def logical_product_variant_path( + logical_product: str, + variant: str, + ) -> str: + """Returns a fully-qualified logical_product_variant string.""" + return "logicalProducts/{logical_product}/variants/{variant}".format( + logical_product=logical_product, + variant=variant, + ) + + @staticmethod + def parse_logical_product_variant_path(path: str) -> Dict[str, str]: + """Parses a logical_product_variant path into its component segments.""" + m = re.match( + r"^logicalProducts/(?P.+?)/variants/(?P.+?)$", + path, + ) + return m.groupdict() if m else {} + + @staticmethod + def product_suite_path( + product_suite: str, + ) -> str: + """Returns a fully-qualified product_suite string.""" + return "productSuites/{product_suite}".format( + product_suite=product_suite, + ) + + @staticmethod + def parse_product_suite_path(path: str) -> Dict[str, str]: + """Parses a product_suite path into its component segments.""" + m = re.match(r"^productSuites/(?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 = ( + CloudProductRegistryReadServiceClient._use_client_cert_effective() + ) + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") + 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: + 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 = ( + CloudProductRegistryReadServiceClient._use_client_cert_effective() + ) + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() + universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") + 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, 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 + ) -> str: + """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 = CloudProductRegistryReadServiceClient._DEFAULT_UNIVERSE + if universe_domain != _default_universe: + raise MutualTLSChannelError( + f"mTLS is not supported in any universe other than {_default_universe}." + ) + api_endpoint = CloudProductRegistryReadServiceClient.DEFAULT_MTLS_ENDPOINT + else: + api_endpoint = ( + CloudProductRegistryReadServiceClient._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 = CloudProductRegistryReadServiceClient._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) -> str: + """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, + CloudProductRegistryReadServiceTransport, + Callable[..., CloudProductRegistryReadServiceTransport], + ] + ] = None, + client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiates the cloud product registry read 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,CloudProductRegistryReadServiceTransport,Callable[..., CloudProductRegistryReadServiceTransport]]]): + 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 CloudProductRegistryReadServiceTransport 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 = ( + CloudProductRegistryReadServiceClient._read_environment_variables() + ) + self._client_cert_source = ( + CloudProductRegistryReadServiceClient._get_client_cert_source( + self._client_options.client_cert_source, self._use_client_cert + ) + ) + self._universe_domain = ( + CloudProductRegistryReadServiceClient._get_universe_domain( + universe_domain_opt, self._universe_domain_env + ) + ) + self._api_endpoint: str = "" # 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, CloudProductRegistryReadServiceTransport + ) + if transport_provided: + # transport is a CloudProductRegistryReadServiceTransport 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(CloudProductRegistryReadServiceTransport, transport) + self._api_endpoint = self._transport.host + + self._api_endpoint = ( + self._api_endpoint + or CloudProductRegistryReadServiceClient._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[CloudProductRegistryReadServiceTransport], + Callable[..., CloudProductRegistryReadServiceTransport], + ] = ( + CloudProductRegistryReadServiceClient.get_transport_class(transport) + if isinstance(transport, str) or transport is None + else cast( + Callable[..., CloudProductRegistryReadServiceTransport], 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.cloud.productregistry_v1.CloudProductRegistryReadServiceClient`.", + extra={ + "serviceName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "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.cloud.productregistry.v1.CloudProductRegistryReadService", + "credentialsType": None, + }, + ) + + def get_product_suite( + self, + request: Optional[ + Union[cloud_product_registry_read_service.GetProductSuiteRequest, 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]]] = (), + ) -> product_suite.ProductSuite: + r"""Get details of a ProductSuite. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import productregistry_v1 + + def sample_get_product_suite(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceClient() + + # Initialize request argument(s) + request = productregistry_v1.GetProductSuiteRequest( + name="name_value", + ) + + # Make the request + response = client.get_product_suite(request=request) + + # Handle the response + print(response) + + Args: + request (Union[google.cloud.productregistry_v1.types.GetProductSuiteRequest, dict]): + The request object. Request message for GetProductSuite. + name (str): + Required. The name of the ProductSuite to retrieve. + Format: productSuites/{product_suite} + + 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.cloud.productregistry_v1.types.ProductSuite: + Represents a unified grouping of + products sharing a common brand and + market positioning. + + """ + # 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, cloud_product_registry_read_service.GetProductSuiteRequest + ): + request = cloud_product_registry_read_service.GetProductSuiteRequest( + 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_suite] + + # 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_product_suites( + self, + request: Optional[ + Union[cloud_product_registry_read_service.ListProductSuitesRequest, dict] + ] = None, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> pagers.ListProductSuitesPager: + r"""Lists ProductSuites. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import productregistry_v1 + + def sample_list_product_suites(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceClient() + + # Initialize request argument(s) + request = productregistry_v1.ListProductSuitesRequest( + ) + + # Make the request + page_result = client.list_product_suites(request=request) + + # Handle the response + for response in page_result: + print(response) + + Args: + request (Union[google.cloud.productregistry_v1.types.ListProductSuitesRequest, dict]): + The request object. Request message for + ListProductSuites. + 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.cloud.productregistry_v1.services.cloud_product_registry_read_service.pagers.ListProductSuitesPager: + Response message for + ListProductSuites. + Iterating over this object will yield + results and resolve additional pages + automatically. + + """ + # 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, cloud_product_registry_read_service.ListProductSuitesRequest + ): + request = cloud_product_registry_read_service.ListProductSuitesRequest( + request + ) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.list_product_suites] + + # 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.ListProductSuitesPager( + method=rpc, + request=request, + response=response, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Done; return the response. + return response + + def get_logical_product( + self, + request: Optional[ + Union[cloud_product_registry_read_service.GetLogicalProductRequest, 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]]] = (), + ) -> logical_product.LogicalProduct: + r"""Gets details of a LogicalProduct. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import productregistry_v1 + + def sample_get_logical_product(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceClient() + + # Initialize request argument(s) + request = productregistry_v1.GetLogicalProductRequest( + name="name_value", + ) + + # Make the request + response = client.get_logical_product(request=request) + + # Handle the response + print(response) + + Args: + request (Union[google.cloud.productregistry_v1.types.GetLogicalProductRequest, dict]): + The request object. Request message for + GetLogicalProduct. + name (str): + Required. The name of the LogicalProduct to retrieve. + Format: logicalProducts/{logical_product} + + 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.cloud.productregistry_v1.types.LogicalProduct: + Represents an independent service + offering that can be provisioned by a + customer. + + """ + # 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, cloud_product_registry_read_service.GetLogicalProductRequest + ): + request = cloud_product_registry_read_service.GetLogicalProductRequest( + 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_logical_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_logical_products( + self, + request: Optional[ + Union[cloud_product_registry_read_service.ListLogicalProductsRequest, dict] + ] = None, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> pagers.ListLogicalProductsPager: + r"""Lists LogicalProducts matching given criteria. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import productregistry_v1 + + def sample_list_logical_products(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceClient() + + # Initialize request argument(s) + request = productregistry_v1.ListLogicalProductsRequest( + ) + + # Make the request + page_result = client.list_logical_products(request=request) + + # Handle the response + for response in page_result: + print(response) + + Args: + request (Union[google.cloud.productregistry_v1.types.ListLogicalProductsRequest, dict]): + The request object. Request message for + ListLogicalProducts. + 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.cloud.productregistry_v1.services.cloud_product_registry_read_service.pagers.ListLogicalProductsPager: + Response message for + ListLogicalProducts. + Iterating over this object will yield + results and resolve additional pages + automatically. + + """ + # 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, cloud_product_registry_read_service.ListLogicalProductsRequest + ): + request = cloud_product_registry_read_service.ListLogicalProductsRequest( + request + ) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.list_logical_products] + + # 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.ListLogicalProductsPager( + method=rpc, + request=request, + response=response, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Done; return the response. + return response + + def get_logical_product_variant( + self, + request: Optional[ + Union[ + cloud_product_registry_read_service.GetLogicalProductVariantRequest, + 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]]] = (), + ) -> logical_product_variant.LogicalProductVariant: + r"""Get details of a LogicalProductVariant. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import productregistry_v1 + + def sample_get_logical_product_variant(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceClient() + + # Initialize request argument(s) + request = productregistry_v1.GetLogicalProductVariantRequest( + name="name_value", + ) + + # Make the request + response = client.get_logical_product_variant(request=request) + + # Handle the response + print(response) + + Args: + request (Union[google.cloud.productregistry_v1.types.GetLogicalProductVariantRequest, dict]): + The request object. Request message for + GetLogicalProductVariant. + name (str): + Required. The name of the LogicalProductVariant to + retrieve. Format: + logicalProducts/{logical_product}/variants/{variant} + + 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.cloud.productregistry_v1.types.LogicalProductVariant: + Represents a distinct offering + derived from a primary product that + retains core functionalities but offers + specialized features for a specific + market segment. + + """ + # 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, cloud_product_registry_read_service.GetLogicalProductVariantRequest + ): + request = ( + cloud_product_registry_read_service.GetLogicalProductVariantRequest( + 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_logical_product_variant + ] + + # 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_logical_product_variants( + self, + request: Optional[ + Union[ + cloud_product_registry_read_service.ListLogicalProductVariantsRequest, + 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.ListLogicalProductVariantsPager: + r"""Lists LogicalProductVariants matching given criteria. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import productregistry_v1 + + def sample_list_logical_product_variants(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceClient() + + # Initialize request argument(s) + request = productregistry_v1.ListLogicalProductVariantsRequest( + parent="parent_value", + ) + + # Make the request + page_result = client.list_logical_product_variants(request=request) + + # Handle the response + for response in page_result: + print(response) + + Args: + request (Union[google.cloud.productregistry_v1.types.ListLogicalProductVariantsRequest, dict]): + The request object. Request message for + ListLogicalProductVariants. + parent (str): + Required. Parent logical product id. Format: + logicalProducts/{logical_product} + + 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.cloud.productregistry_v1.services.cloud_product_registry_read_service.pagers.ListLogicalProductVariantsPager: + Response message for + ListLogicalProductVariants. + 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, + cloud_product_registry_read_service.ListLogicalProductVariantsRequest, + ): + request = ( + cloud_product_registry_read_service.ListLogicalProductVariantsRequest( + 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_logical_product_variants + ] + + # 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.ListLogicalProductVariantsPager( + method=rpc, + request=request, + response=response, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Done; return the response. + return response + + def lookup_entity( + self, + request: Optional[ + Union[cloud_product_registry_read_service.LookupEntityRequest, dict] + ] = None, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> cloud_product_registry_read_service.LookupEntityResponse: + r"""Look up entities. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import productregistry_v1 + + def sample_lookup_entity(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceClient() + + # Initialize request argument(s) + request = productregistry_v1.LookupEntityRequest( + lookup_uri="lookup_uri_value", + ) + + # Make the request + response = client.lookup_entity(request=request) + + # Handle the response + print(response) + + Args: + request (Union[google.cloud.productregistry_v1.types.LookupEntityRequest, dict]): + The request object. Request message for LookupEntity. + 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.cloud.productregistry_v1.types.LookupEntityResponse: + Response message for LookupEntity. + """ + # 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, cloud_product_registry_read_service.LookupEntityRequest + ): + request = cloud_product_registry_read_service.LookupEntityRequest(request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.lookup_entity] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata( + (("lookup_uri", request.lookup_uri),) + ), + ) + + # 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 __enter__(self) -> "CloudProductRegistryReadServiceClient": + 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__ = ("CloudProductRegistryReadServiceClient",) diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/pagers.py b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/pagers.py new file mode 100644 index 000000000000..60d5d295fc76 --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/pagers.py @@ -0,0 +1,563 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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.cloud.productregistry_v1.types import ( + cloud_product_registry_read_service, + logical_product, + logical_product_variant, + product_suite, +) + + +class ListProductSuitesPager: + """A pager for iterating through ``list_product_suites`` requests. + + This class thinly wraps an initial + :class:`google.cloud.productregistry_v1.types.ListProductSuitesResponse` object, and + provides an ``__iter__`` method to iterate through its + ``product_suites`` field. + + If there are more pages, the ``__iter__`` method will make additional + ``ListProductSuites`` requests and continue to iterate + through the ``product_suites`` field on the + corresponding responses. + + All the usual :class:`google.cloud.productregistry_v1.types.ListProductSuitesResponse` + 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[ + ..., cloud_product_registry_read_service.ListProductSuitesResponse + ], + request: cloud_product_registry_read_service.ListProductSuitesRequest, + response: cloud_product_registry_read_service.ListProductSuitesResponse, + *, + 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.cloud.productregistry_v1.types.ListProductSuitesRequest): + The initial request object. + response (google.cloud.productregistry_v1.types.ListProductSuitesResponse): + 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 = cloud_product_registry_read_service.ListProductSuitesRequest( + 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[cloud_product_registry_read_service.ListProductSuitesResponse]: + 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[product_suite.ProductSuite]: + for page in self.pages: + yield from page.product_suites + + def __repr__(self) -> str: + return "{0}<{1!r}>".format(self.__class__.__name__, self._response) + + +class ListProductSuitesAsyncPager: + """A pager for iterating through ``list_product_suites`` requests. + + This class thinly wraps an initial + :class:`google.cloud.productregistry_v1.types.ListProductSuitesResponse` object, and + provides an ``__aiter__`` method to iterate through its + ``product_suites`` field. + + If there are more pages, the ``__aiter__`` method will make additional + ``ListProductSuites`` requests and continue to iterate + through the ``product_suites`` field on the + corresponding responses. + + All the usual :class:`google.cloud.productregistry_v1.types.ListProductSuitesResponse` + 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[cloud_product_registry_read_service.ListProductSuitesResponse], + ], + request: cloud_product_registry_read_service.ListProductSuitesRequest, + response: cloud_product_registry_read_service.ListProductSuitesResponse, + *, + 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.cloud.productregistry_v1.types.ListProductSuitesRequest): + The initial request object. + response (google.cloud.productregistry_v1.types.ListProductSuitesResponse): + 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 = cloud_product_registry_read_service.ListProductSuitesRequest( + 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[cloud_product_registry_read_service.ListProductSuitesResponse]: + 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[product_suite.ProductSuite]: + async def async_generator(): + async for page in self.pages: + for response in page.product_suites: + yield response + + return async_generator() + + def __repr__(self) -> str: + return "{0}<{1!r}>".format(self.__class__.__name__, self._response) + + +class ListLogicalProductsPager: + """A pager for iterating through ``list_logical_products`` requests. + + This class thinly wraps an initial + :class:`google.cloud.productregistry_v1.types.ListLogicalProductsResponse` object, and + provides an ``__iter__`` method to iterate through its + ``logical_products`` field. + + If there are more pages, the ``__iter__`` method will make additional + ``ListLogicalProducts`` requests and continue to iterate + through the ``logical_products`` field on the + corresponding responses. + + All the usual :class:`google.cloud.productregistry_v1.types.ListLogicalProductsResponse` + 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[ + ..., cloud_product_registry_read_service.ListLogicalProductsResponse + ], + request: cloud_product_registry_read_service.ListLogicalProductsRequest, + response: cloud_product_registry_read_service.ListLogicalProductsResponse, + *, + 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.cloud.productregistry_v1.types.ListLogicalProductsRequest): + The initial request object. + response (google.cloud.productregistry_v1.types.ListLogicalProductsResponse): + 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 = cloud_product_registry_read_service.ListLogicalProductsRequest( + 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[cloud_product_registry_read_service.ListLogicalProductsResponse]: + 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[logical_product.LogicalProduct]: + for page in self.pages: + yield from page.logical_products + + def __repr__(self) -> str: + return "{0}<{1!r}>".format(self.__class__.__name__, self._response) + + +class ListLogicalProductsAsyncPager: + """A pager for iterating through ``list_logical_products`` requests. + + This class thinly wraps an initial + :class:`google.cloud.productregistry_v1.types.ListLogicalProductsResponse` object, and + provides an ``__aiter__`` method to iterate through its + ``logical_products`` field. + + If there are more pages, the ``__aiter__`` method will make additional + ``ListLogicalProducts`` requests and continue to iterate + through the ``logical_products`` field on the + corresponding responses. + + All the usual :class:`google.cloud.productregistry_v1.types.ListLogicalProductsResponse` + 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[cloud_product_registry_read_service.ListLogicalProductsResponse], + ], + request: cloud_product_registry_read_service.ListLogicalProductsRequest, + response: cloud_product_registry_read_service.ListLogicalProductsResponse, + *, + 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.cloud.productregistry_v1.types.ListLogicalProductsRequest): + The initial request object. + response (google.cloud.productregistry_v1.types.ListLogicalProductsResponse): + 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 = cloud_product_registry_read_service.ListLogicalProductsRequest( + 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[cloud_product_registry_read_service.ListLogicalProductsResponse]: + 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[logical_product.LogicalProduct]: + async def async_generator(): + async for page in self.pages: + for response in page.logical_products: + yield response + + return async_generator() + + def __repr__(self) -> str: + return "{0}<{1!r}>".format(self.__class__.__name__, self._response) + + +class ListLogicalProductVariantsPager: + """A pager for iterating through ``list_logical_product_variants`` requests. + + This class thinly wraps an initial + :class:`google.cloud.productregistry_v1.types.ListLogicalProductVariantsResponse` object, and + provides an ``__iter__`` method to iterate through its + ``logical_product_variants`` field. + + If there are more pages, the ``__iter__`` method will make additional + ``ListLogicalProductVariants`` requests and continue to iterate + through the ``logical_product_variants`` field on the + corresponding responses. + + All the usual :class:`google.cloud.productregistry_v1.types.ListLogicalProductVariantsResponse` + 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[ + ..., cloud_product_registry_read_service.ListLogicalProductVariantsResponse + ], + request: cloud_product_registry_read_service.ListLogicalProductVariantsRequest, + response: cloud_product_registry_read_service.ListLogicalProductVariantsResponse, + *, + 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.cloud.productregistry_v1.types.ListLogicalProductVariantsRequest): + The initial request object. + response (google.cloud.productregistry_v1.types.ListLogicalProductVariantsResponse): + 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 = ( + cloud_product_registry_read_service.ListLogicalProductVariantsRequest( + 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[ + cloud_product_registry_read_service.ListLogicalProductVariantsResponse + ]: + 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[logical_product_variant.LogicalProductVariant]: + for page in self.pages: + yield from page.logical_product_variants + + def __repr__(self) -> str: + return "{0}<{1!r}>".format(self.__class__.__name__, self._response) + + +class ListLogicalProductVariantsAsyncPager: + """A pager for iterating through ``list_logical_product_variants`` requests. + + This class thinly wraps an initial + :class:`google.cloud.productregistry_v1.types.ListLogicalProductVariantsResponse` object, and + provides an ``__aiter__`` method to iterate through its + ``logical_product_variants`` field. + + If there are more pages, the ``__aiter__`` method will make additional + ``ListLogicalProductVariants`` requests and continue to iterate + through the ``logical_product_variants`` field on the + corresponding responses. + + All the usual :class:`google.cloud.productregistry_v1.types.ListLogicalProductVariantsResponse` + 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[ + cloud_product_registry_read_service.ListLogicalProductVariantsResponse + ], + ], + request: cloud_product_registry_read_service.ListLogicalProductVariantsRequest, + response: cloud_product_registry_read_service.ListLogicalProductVariantsResponse, + *, + 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.cloud.productregistry_v1.types.ListLogicalProductVariantsRequest): + The initial request object. + response (google.cloud.productregistry_v1.types.ListLogicalProductVariantsResponse): + 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 = ( + cloud_product_registry_read_service.ListLogicalProductVariantsRequest( + 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[ + cloud_product_registry_read_service.ListLogicalProductVariantsResponse + ]: + 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[logical_product_variant.LogicalProductVariant]: + async def async_generator(): + async for page in self.pages: + for response in page.logical_product_variants: + yield response + + return async_generator() + + def __repr__(self) -> str: + return "{0}<{1!r}>".format(self.__class__.__name__, self._response) diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/README.rst b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/README.rst new file mode 100644 index 000000000000..02a042597ec0 --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/README.rst @@ -0,0 +1,10 @@ + +transport inheritance structure +_______________________________ + +``CloudProductRegistryReadServiceTransport`` is the ABC for all transports. + +- public child ``CloudProductRegistryReadServiceGrpcTransport`` for sync gRPC transport (defined in ``grpc.py``). +- public child ``CloudProductRegistryReadServiceGrpcAsyncIOTransport`` for async gRPC transport (defined in ``grpc_asyncio.py``). +- private child ``_BaseCloudProductRegistryReadServiceRestTransport`` for base REST transport with inner classes ``_BaseMETHOD`` (defined in ``rest_base.py``). +- public child ``CloudProductRegistryReadServiceRestTransport`` 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-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/__init__.py b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/__init__.py new file mode 100644 index 000000000000..21db4fc3558a --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/__init__.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 CloudProductRegistryReadServiceTransport +from .grpc import CloudProductRegistryReadServiceGrpcTransport +from .grpc_asyncio import CloudProductRegistryReadServiceGrpcAsyncIOTransport +from .rest import ( + CloudProductRegistryReadServiceRestInterceptor, + CloudProductRegistryReadServiceRestTransport, +) + +# Compile a registry of transports. +_transport_registry = OrderedDict() # type: Dict[str, Type[CloudProductRegistryReadServiceTransport]] +_transport_registry["grpc"] = CloudProductRegistryReadServiceGrpcTransport +_transport_registry["grpc_asyncio"] = ( + CloudProductRegistryReadServiceGrpcAsyncIOTransport +) +_transport_registry["rest"] = CloudProductRegistryReadServiceRestTransport + +__all__ = ( + "CloudProductRegistryReadServiceTransport", + "CloudProductRegistryReadServiceGrpcTransport", + "CloudProductRegistryReadServiceGrpcAsyncIOTransport", + "CloudProductRegistryReadServiceRestTransport", + "CloudProductRegistryReadServiceRestInterceptor", +) diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/base.py b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/base.py new file mode 100644 index 000000000000..1f3fa13184dd --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/base.py @@ -0,0 +1,346 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 +import google.auth # type: ignore +import google.protobuf +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.oauth2 import service_account # type: ignore + +from google.cloud.productregistry_v1 import gapic_version as package_version +from google.cloud.productregistry_v1.types import ( + cloud_product_registry_read_service, + logical_product, + logical_product_variant, + product_suite, +) + +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 CloudProductRegistryReadServiceTransport(abc.ABC): + """Abstract transport class for CloudProductRegistryReadService.""" + + AUTH_SCOPES = () + + DEFAULT_HOST: str = "cloudproductregistry.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: 'cloudproductregistry.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]): Deprecated. A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. This argument will be + removed in the next major version of this library. + 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. + api_audience (Optional[str]): The intended audience for the API calls + to the service that will be set when using certain 3rd party + authentication flows. Audience is typically a resource identifier. + If not set, the host value will be used as a default. + """ + + # 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=scopes, + quota_project_id=quota_project_id, + default_scopes=self.AUTH_SCOPES, + ) + elif credentials is None and not self._ignore_credentials: + credentials, _ = google.auth.default( + scopes=scopes, + quota_project_id=quota_project_id, + default_scopes=self.AUTH_SCOPES, + ) + # 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 + + self._wrapped_methods: Dict[Callable, Callable] = {} + + @property + def host(self): + return self._host + + def _prep_wrapped_messages(self, client_info): + # Precompute the wrapped methods. + self._wrapped_methods = { + self.get_product_suite: gapic_v1.method.wrap_method( + self.get_product_suite, + default_retry=retries.Retry( + initial=1.0, + maximum=10.0, + multiplier=1.3, + predicate=retries.if_exception_type( + core_exceptions.ServiceUnavailable, + ), + deadline=60.0, + ), + default_timeout=60.0, + client_info=client_info, + ), + self.list_product_suites: gapic_v1.method.wrap_method( + self.list_product_suites, + default_retry=retries.Retry( + initial=1.0, + maximum=10.0, + multiplier=1.3, + predicate=retries.if_exception_type( + core_exceptions.ServiceUnavailable, + ), + deadline=60.0, + ), + default_timeout=60.0, + client_info=client_info, + ), + self.get_logical_product: gapic_v1.method.wrap_method( + self.get_logical_product, + default_retry=retries.Retry( + initial=1.0, + maximum=10.0, + multiplier=1.3, + predicate=retries.if_exception_type( + core_exceptions.ServiceUnavailable, + ), + deadline=60.0, + ), + default_timeout=60.0, + client_info=client_info, + ), + self.list_logical_products: gapic_v1.method.wrap_method( + self.list_logical_products, + default_retry=retries.Retry( + initial=1.0, + maximum=10.0, + multiplier=1.3, + predicate=retries.if_exception_type( + core_exceptions.ServiceUnavailable, + ), + deadline=60.0, + ), + default_timeout=60.0, + client_info=client_info, + ), + self.get_logical_product_variant: gapic_v1.method.wrap_method( + self.get_logical_product_variant, + default_retry=retries.Retry( + initial=1.0, + maximum=10.0, + multiplier=1.3, + predicate=retries.if_exception_type( + core_exceptions.ServiceUnavailable, + ), + deadline=60.0, + ), + default_timeout=60.0, + client_info=client_info, + ), + self.list_logical_product_variants: gapic_v1.method.wrap_method( + self.list_logical_product_variants, + default_retry=retries.Retry( + initial=1.0, + maximum=10.0, + multiplier=1.3, + predicate=retries.if_exception_type( + core_exceptions.ServiceUnavailable, + ), + deadline=60.0, + ), + default_timeout=60.0, + client_info=client_info, + ), + self.lookup_entity: gapic_v1.method.wrap_method( + self.lookup_entity, + default_retry=retries.Retry( + initial=1.0, + maximum=10.0, + multiplier=1.3, + predicate=retries.if_exception_type( + core_exceptions.ServiceUnavailable, + ), + deadline=60.0, + ), + default_timeout=60.0, + 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_suite( + self, + ) -> Callable[ + [cloud_product_registry_read_service.GetProductSuiteRequest], + Union[product_suite.ProductSuite, Awaitable[product_suite.ProductSuite]], + ]: + raise NotImplementedError() + + @property + def list_product_suites( + self, + ) -> Callable[ + [cloud_product_registry_read_service.ListProductSuitesRequest], + Union[ + cloud_product_registry_read_service.ListProductSuitesResponse, + Awaitable[cloud_product_registry_read_service.ListProductSuitesResponse], + ], + ]: + raise NotImplementedError() + + @property + def get_logical_product( + self, + ) -> Callable[ + [cloud_product_registry_read_service.GetLogicalProductRequest], + Union[ + logical_product.LogicalProduct, Awaitable[logical_product.LogicalProduct] + ], + ]: + raise NotImplementedError() + + @property + def list_logical_products( + self, + ) -> Callable[ + [cloud_product_registry_read_service.ListLogicalProductsRequest], + Union[ + cloud_product_registry_read_service.ListLogicalProductsResponse, + Awaitable[cloud_product_registry_read_service.ListLogicalProductsResponse], + ], + ]: + raise NotImplementedError() + + @property + def get_logical_product_variant( + self, + ) -> Callable[ + [cloud_product_registry_read_service.GetLogicalProductVariantRequest], + Union[ + logical_product_variant.LogicalProductVariant, + Awaitable[logical_product_variant.LogicalProductVariant], + ], + ]: + raise NotImplementedError() + + @property + def list_logical_product_variants( + self, + ) -> Callable[ + [cloud_product_registry_read_service.ListLogicalProductVariantsRequest], + Union[ + cloud_product_registry_read_service.ListLogicalProductVariantsResponse, + Awaitable[ + cloud_product_registry_read_service.ListLogicalProductVariantsResponse + ], + ], + ]: + raise NotImplementedError() + + @property + def lookup_entity( + self, + ) -> Callable[ + [cloud_product_registry_read_service.LookupEntityRequest], + Union[ + cloud_product_registry_read_service.LookupEntityResponse, + Awaitable[cloud_product_registry_read_service.LookupEntityResponse], + ], + ]: + raise NotImplementedError() + + @property + def kind(self) -> str: + raise NotImplementedError() + + +__all__ = ("CloudProductRegistryReadServiceTransport",) diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/grpc.py b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/grpc.py new file mode 100644 index 000000000000..7421b73a8ffd --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/grpc.py @@ -0,0 +1,551 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 +import warnings +from typing import Callable, Dict, Optional, Sequence, Tuple, Union + +import google.auth # type: ignore +import google.protobuf.message +import grpc # type: ignore +import proto # type: ignore +from google.api_core import gapic_v1, grpc_helpers +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 + +from google.cloud.productregistry_v1.types import ( + cloud_product_registry_read_service, + logical_product, + logical_product_variant, + product_suite, +) + +from .base import DEFAULT_CLIENT_INFO, CloudProductRegistryReadServiceTransport + +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)!r}" + + 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.cloud.productregistry.v1.CloudProductRegistryReadService", + "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)!r}" + grpc_response = { + "payload": response_payload, + "metadata": metadata, + "status": "OK", + } + _LOGGER.debug( + f"Received response for {client_call_details.method}.", + extra={ + "serviceName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "rpcName": client_call_details.method, + "response": grpc_response, + "metadata": grpc_response["metadata"], + }, + ) + return response + + +class CloudProductRegistryReadServiceGrpcTransport( + CloudProductRegistryReadServiceTransport +): + """gRPC backend transport for CloudProductRegistryReadService. + + Cloud Product Registry Read Service provides capabilities to + access all first and third party Google Cloud products. + + 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 = "cloudproductregistry.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: 'cloudproductregistry.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]): Deprecated. 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. + This argument will be removed in the next major version of this library. + 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. + api_audience (Optional[str]): The intended audience for the API calls + to the service that will be set when using certain 3rd party + authentication flows. Audience is typically a resource identifier. + If not set, the host value will be used as a default. + + 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 = "cloudproductregistry.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]): Deprecated. A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. This argument will be + removed in the next major version of this library. + 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_suite( + self, + ) -> Callable[ + [cloud_product_registry_read_service.GetProductSuiteRequest], + product_suite.ProductSuite, + ]: + r"""Return a callable for the get product suite method over gRPC. + + Get details of a ProductSuite. + + Returns: + Callable[[~.GetProductSuiteRequest], + ~.ProductSuite]: + 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_suite" not in self._stubs: + self._stubs["get_product_suite"] = self._logged_channel.unary_unary( + "/google.cloud.productregistry.v1.CloudProductRegistryReadService/GetProductSuite", + request_serializer=cloud_product_registry_read_service.GetProductSuiteRequest.serialize, + response_deserializer=product_suite.ProductSuite.deserialize, + ) + return self._stubs["get_product_suite"] + + @property + def list_product_suites( + self, + ) -> Callable[ + [cloud_product_registry_read_service.ListProductSuitesRequest], + cloud_product_registry_read_service.ListProductSuitesResponse, + ]: + r"""Return a callable for the list product suites method over gRPC. + + Lists ProductSuites. + + Returns: + Callable[[~.ListProductSuitesRequest], + ~.ListProductSuitesResponse]: + 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_product_suites" not in self._stubs: + self._stubs["list_product_suites"] = self._logged_channel.unary_unary( + "/google.cloud.productregistry.v1.CloudProductRegistryReadService/ListProductSuites", + request_serializer=cloud_product_registry_read_service.ListProductSuitesRequest.serialize, + response_deserializer=cloud_product_registry_read_service.ListProductSuitesResponse.deserialize, + ) + return self._stubs["list_product_suites"] + + @property + def get_logical_product( + self, + ) -> Callable[ + [cloud_product_registry_read_service.GetLogicalProductRequest], + logical_product.LogicalProduct, + ]: + r"""Return a callable for the get logical product method over gRPC. + + Gets details of a LogicalProduct. + + Returns: + Callable[[~.GetLogicalProductRequest], + ~.LogicalProduct]: + 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_logical_product" not in self._stubs: + self._stubs["get_logical_product"] = self._logged_channel.unary_unary( + "/google.cloud.productregistry.v1.CloudProductRegistryReadService/GetLogicalProduct", + request_serializer=cloud_product_registry_read_service.GetLogicalProductRequest.serialize, + response_deserializer=logical_product.LogicalProduct.deserialize, + ) + return self._stubs["get_logical_product"] + + @property + def list_logical_products( + self, + ) -> Callable[ + [cloud_product_registry_read_service.ListLogicalProductsRequest], + cloud_product_registry_read_service.ListLogicalProductsResponse, + ]: + r"""Return a callable for the list logical products method over gRPC. + + Lists LogicalProducts matching given criteria. + + Returns: + Callable[[~.ListLogicalProductsRequest], + ~.ListLogicalProductsResponse]: + 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_logical_products" not in self._stubs: + self._stubs["list_logical_products"] = self._logged_channel.unary_unary( + "/google.cloud.productregistry.v1.CloudProductRegistryReadService/ListLogicalProducts", + request_serializer=cloud_product_registry_read_service.ListLogicalProductsRequest.serialize, + response_deserializer=cloud_product_registry_read_service.ListLogicalProductsResponse.deserialize, + ) + return self._stubs["list_logical_products"] + + @property + def get_logical_product_variant( + self, + ) -> Callable[ + [cloud_product_registry_read_service.GetLogicalProductVariantRequest], + logical_product_variant.LogicalProductVariant, + ]: + r"""Return a callable for the get logical product variant method over gRPC. + + Get details of a LogicalProductVariant. + + Returns: + Callable[[~.GetLogicalProductVariantRequest], + ~.LogicalProductVariant]: + 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_logical_product_variant" not in self._stubs: + self._stubs["get_logical_product_variant"] = ( + self._logged_channel.unary_unary( + "/google.cloud.productregistry.v1.CloudProductRegistryReadService/GetLogicalProductVariant", + request_serializer=cloud_product_registry_read_service.GetLogicalProductVariantRequest.serialize, + response_deserializer=logical_product_variant.LogicalProductVariant.deserialize, + ) + ) + return self._stubs["get_logical_product_variant"] + + @property + def list_logical_product_variants( + self, + ) -> Callable[ + [cloud_product_registry_read_service.ListLogicalProductVariantsRequest], + cloud_product_registry_read_service.ListLogicalProductVariantsResponse, + ]: + r"""Return a callable for the list logical product variants method over gRPC. + + Lists LogicalProductVariants matching given criteria. + + Returns: + Callable[[~.ListLogicalProductVariantsRequest], + ~.ListLogicalProductVariantsResponse]: + 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_logical_product_variants" not in self._stubs: + self._stubs["list_logical_product_variants"] = ( + self._logged_channel.unary_unary( + "/google.cloud.productregistry.v1.CloudProductRegistryReadService/ListLogicalProductVariants", + request_serializer=cloud_product_registry_read_service.ListLogicalProductVariantsRequest.serialize, + response_deserializer=cloud_product_registry_read_service.ListLogicalProductVariantsResponse.deserialize, + ) + ) + return self._stubs["list_logical_product_variants"] + + @property + def lookup_entity( + self, + ) -> Callable[ + [cloud_product_registry_read_service.LookupEntityRequest], + cloud_product_registry_read_service.LookupEntityResponse, + ]: + r"""Return a callable for the lookup entity method over gRPC. + + Look up entities. + + Returns: + Callable[[~.LookupEntityRequest], + ~.LookupEntityResponse]: + 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 "lookup_entity" not in self._stubs: + self._stubs["lookup_entity"] = self._logged_channel.unary_unary( + "/google.cloud.productregistry.v1.CloudProductRegistryReadService/LookupEntity", + request_serializer=cloud_product_registry_read_service.LookupEntityRequest.serialize, + response_deserializer=cloud_product_registry_read_service.LookupEntityResponse.deserialize, + ) + return self._stubs["lookup_entity"] + + def close(self): + self._logged_channel.close() + + @property + def kind(self) -> str: + return "grpc" + + +__all__ = ("CloudProductRegistryReadServiceGrpcTransport",) diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/grpc_asyncio.py b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/grpc_asyncio.py new file mode 100644 index 000000000000..22d0675b092b --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/grpc_asyncio.py @@ -0,0 +1,669 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 +import warnings +from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union + +import google.protobuf.message +import grpc # type: ignore +import proto # type: ignore +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 +from grpc.experimental import aio # type: ignore + +from google.cloud.productregistry_v1.types import ( + cloud_product_registry_read_service, + logical_product, + logical_product_variant, + product_suite, +) + +from .base import DEFAULT_CLIENT_INFO, CloudProductRegistryReadServiceTransport +from .grpc import CloudProductRegistryReadServiceGrpcTransport + +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)!r}" + + 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.cloud.productregistry.v1.CloudProductRegistryReadService", + "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)!r}" + grpc_response = { + "payload": response_payload, + "metadata": metadata, + "status": "OK", + } + _LOGGER.debug( + f"Received response to rpc {client_call_details.method}.", + extra={ + "serviceName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "rpcName": str(client_call_details.method), + "response": grpc_response, + "metadata": grpc_response["metadata"], + }, + ) + return response + + +class CloudProductRegistryReadServiceGrpcAsyncIOTransport( + CloudProductRegistryReadServiceTransport +): + """gRPC AsyncIO backend transport for CloudProductRegistryReadService. + + Cloud Product Registry Read Service provides capabilities to + access all first and third party Google Cloud products. + + 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 = "cloudproductregistry.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]): Deprecated. A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be + removed in the next major version of this library. + 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 = "cloudproductregistry.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: 'cloudproductregistry.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]): Deprecated. 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. + This argument will be removed in the next major version of this library. + 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. + api_audience (Optional[str]): The intended audience for the API calls + to the service that will be set when using certain 3rd party + authentication flows. Audience is typically a resource identifier. + If not set, the host value will be used as a default. + + 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_suite( + self, + ) -> Callable[ + [cloud_product_registry_read_service.GetProductSuiteRequest], + Awaitable[product_suite.ProductSuite], + ]: + r"""Return a callable for the get product suite method over gRPC. + + Get details of a ProductSuite. + + Returns: + Callable[[~.GetProductSuiteRequest], + Awaitable[~.ProductSuite]]: + 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_suite" not in self._stubs: + self._stubs["get_product_suite"] = self._logged_channel.unary_unary( + "/google.cloud.productregistry.v1.CloudProductRegistryReadService/GetProductSuite", + request_serializer=cloud_product_registry_read_service.GetProductSuiteRequest.serialize, + response_deserializer=product_suite.ProductSuite.deserialize, + ) + return self._stubs["get_product_suite"] + + @property + def list_product_suites( + self, + ) -> Callable[ + [cloud_product_registry_read_service.ListProductSuitesRequest], + Awaitable[cloud_product_registry_read_service.ListProductSuitesResponse], + ]: + r"""Return a callable for the list product suites method over gRPC. + + Lists ProductSuites. + + Returns: + Callable[[~.ListProductSuitesRequest], + Awaitable[~.ListProductSuitesResponse]]: + 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_product_suites" not in self._stubs: + self._stubs["list_product_suites"] = self._logged_channel.unary_unary( + "/google.cloud.productregistry.v1.CloudProductRegistryReadService/ListProductSuites", + request_serializer=cloud_product_registry_read_service.ListProductSuitesRequest.serialize, + response_deserializer=cloud_product_registry_read_service.ListProductSuitesResponse.deserialize, + ) + return self._stubs["list_product_suites"] + + @property + def get_logical_product( + self, + ) -> Callable[ + [cloud_product_registry_read_service.GetLogicalProductRequest], + Awaitable[logical_product.LogicalProduct], + ]: + r"""Return a callable for the get logical product method over gRPC. + + Gets details of a LogicalProduct. + + Returns: + Callable[[~.GetLogicalProductRequest], + Awaitable[~.LogicalProduct]]: + 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_logical_product" not in self._stubs: + self._stubs["get_logical_product"] = self._logged_channel.unary_unary( + "/google.cloud.productregistry.v1.CloudProductRegistryReadService/GetLogicalProduct", + request_serializer=cloud_product_registry_read_service.GetLogicalProductRequest.serialize, + response_deserializer=logical_product.LogicalProduct.deserialize, + ) + return self._stubs["get_logical_product"] + + @property + def list_logical_products( + self, + ) -> Callable[ + [cloud_product_registry_read_service.ListLogicalProductsRequest], + Awaitable[cloud_product_registry_read_service.ListLogicalProductsResponse], + ]: + r"""Return a callable for the list logical products method over gRPC. + + Lists LogicalProducts matching given criteria. + + Returns: + Callable[[~.ListLogicalProductsRequest], + Awaitable[~.ListLogicalProductsResponse]]: + 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_logical_products" not in self._stubs: + self._stubs["list_logical_products"] = self._logged_channel.unary_unary( + "/google.cloud.productregistry.v1.CloudProductRegistryReadService/ListLogicalProducts", + request_serializer=cloud_product_registry_read_service.ListLogicalProductsRequest.serialize, + response_deserializer=cloud_product_registry_read_service.ListLogicalProductsResponse.deserialize, + ) + return self._stubs["list_logical_products"] + + @property + def get_logical_product_variant( + self, + ) -> Callable[ + [cloud_product_registry_read_service.GetLogicalProductVariantRequest], + Awaitable[logical_product_variant.LogicalProductVariant], + ]: + r"""Return a callable for the get logical product variant method over gRPC. + + Get details of a LogicalProductVariant. + + Returns: + Callable[[~.GetLogicalProductVariantRequest], + Awaitable[~.LogicalProductVariant]]: + 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_logical_product_variant" not in self._stubs: + self._stubs["get_logical_product_variant"] = ( + self._logged_channel.unary_unary( + "/google.cloud.productregistry.v1.CloudProductRegistryReadService/GetLogicalProductVariant", + request_serializer=cloud_product_registry_read_service.GetLogicalProductVariantRequest.serialize, + response_deserializer=logical_product_variant.LogicalProductVariant.deserialize, + ) + ) + return self._stubs["get_logical_product_variant"] + + @property + def list_logical_product_variants( + self, + ) -> Callable[ + [cloud_product_registry_read_service.ListLogicalProductVariantsRequest], + Awaitable[ + cloud_product_registry_read_service.ListLogicalProductVariantsResponse + ], + ]: + r"""Return a callable for the list logical product variants method over gRPC. + + Lists LogicalProductVariants matching given criteria. + + Returns: + Callable[[~.ListLogicalProductVariantsRequest], + Awaitable[~.ListLogicalProductVariantsResponse]]: + 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_logical_product_variants" not in self._stubs: + self._stubs["list_logical_product_variants"] = ( + self._logged_channel.unary_unary( + "/google.cloud.productregistry.v1.CloudProductRegistryReadService/ListLogicalProductVariants", + request_serializer=cloud_product_registry_read_service.ListLogicalProductVariantsRequest.serialize, + response_deserializer=cloud_product_registry_read_service.ListLogicalProductVariantsResponse.deserialize, + ) + ) + return self._stubs["list_logical_product_variants"] + + @property + def lookup_entity( + self, + ) -> Callable[ + [cloud_product_registry_read_service.LookupEntityRequest], + Awaitable[cloud_product_registry_read_service.LookupEntityResponse], + ]: + r"""Return a callable for the lookup entity method over gRPC. + + Look up entities. + + Returns: + Callable[[~.LookupEntityRequest], + Awaitable[~.LookupEntityResponse]]: + 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 "lookup_entity" not in self._stubs: + self._stubs["lookup_entity"] = self._logged_channel.unary_unary( + "/google.cloud.productregistry.v1.CloudProductRegistryReadService/LookupEntity", + request_serializer=cloud_product_registry_read_service.LookupEntityRequest.serialize, + response_deserializer=cloud_product_registry_read_service.LookupEntityResponse.deserialize, + ) + return self._stubs["lookup_entity"] + + 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_suite: self._wrap_method( + self.get_product_suite, + default_retry=retries.AsyncRetry( + initial=1.0, + maximum=10.0, + multiplier=1.3, + predicate=retries.if_exception_type( + core_exceptions.ServiceUnavailable, + ), + deadline=60.0, + ), + default_timeout=60.0, + client_info=client_info, + ), + self.list_product_suites: self._wrap_method( + self.list_product_suites, + default_retry=retries.AsyncRetry( + initial=1.0, + maximum=10.0, + multiplier=1.3, + predicate=retries.if_exception_type( + core_exceptions.ServiceUnavailable, + ), + deadline=60.0, + ), + default_timeout=60.0, + client_info=client_info, + ), + self.get_logical_product: self._wrap_method( + self.get_logical_product, + default_retry=retries.AsyncRetry( + initial=1.0, + maximum=10.0, + multiplier=1.3, + predicate=retries.if_exception_type( + core_exceptions.ServiceUnavailable, + ), + deadline=60.0, + ), + default_timeout=60.0, + client_info=client_info, + ), + self.list_logical_products: self._wrap_method( + self.list_logical_products, + default_retry=retries.AsyncRetry( + initial=1.0, + maximum=10.0, + multiplier=1.3, + predicate=retries.if_exception_type( + core_exceptions.ServiceUnavailable, + ), + deadline=60.0, + ), + default_timeout=60.0, + client_info=client_info, + ), + self.get_logical_product_variant: self._wrap_method( + self.get_logical_product_variant, + default_retry=retries.AsyncRetry( + initial=1.0, + maximum=10.0, + multiplier=1.3, + predicate=retries.if_exception_type( + core_exceptions.ServiceUnavailable, + ), + deadline=60.0, + ), + default_timeout=60.0, + client_info=client_info, + ), + self.list_logical_product_variants: self._wrap_method( + self.list_logical_product_variants, + default_retry=retries.AsyncRetry( + initial=1.0, + maximum=10.0, + multiplier=1.3, + predicate=retries.if_exception_type( + core_exceptions.ServiceUnavailable, + ), + deadline=60.0, + ), + default_timeout=60.0, + client_info=client_info, + ), + self.lookup_entity: self._wrap_method( + self.lookup_entity, + default_retry=retries.AsyncRetry( + initial=1.0, + maximum=10.0, + multiplier=1.3, + predicate=retries.if_exception_type( + core_exceptions.ServiceUnavailable, + ), + deadline=60.0, + ), + default_timeout=60.0, + 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__ = ("CloudProductRegistryReadServiceGrpcAsyncIOTransport",) diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/rest.py b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/rest.py new file mode 100644 index 000000000000..bb1fbeeb1549 --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/rest.py @@ -0,0 +1,1756 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 +import warnings +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union + +import google.protobuf +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 +from google.protobuf import json_format +from requests import __version__ as requests_version + +from google.cloud.productregistry_v1.types import ( + cloud_product_registry_read_service, + logical_product, + logical_product_variant, + product_suite, +) + +from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO +from .rest_base import _BaseCloudProductRegistryReadServiceRestTransport + +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 CloudProductRegistryReadServiceRestInterceptor: + """Interceptor for CloudProductRegistryReadService. + + 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 CloudProductRegistryReadServiceRestTransport. + + .. code-block:: python + class MyCustomCloudProductRegistryReadServiceInterceptor(CloudProductRegistryReadServiceRestInterceptor): + def pre_get_logical_product(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_logical_product(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_get_logical_product_variant(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_logical_product_variant(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_get_product_suite(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_product_suite(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_logical_products(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_logical_products(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_logical_product_variants(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_logical_product_variants(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_product_suites(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_product_suites(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_lookup_entity(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_lookup_entity(self, response): + logging.log(f"Received response: {response}") + return response + + transport = CloudProductRegistryReadServiceRestTransport(interceptor=MyCustomCloudProductRegistryReadServiceInterceptor()) + client = CloudProductRegistryReadServiceClient(transport=transport) + + + """ + + def pre_get_logical_product( + self, + request: cloud_product_registry_read_service.GetLogicalProductRequest, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[ + cloud_product_registry_read_service.GetLogicalProductRequest, + Sequence[Tuple[str, Union[str, bytes]]], + ]: + """Pre-rpc interceptor for get_logical_product + + Override in a subclass to manipulate the request or metadata + before they are sent to the CloudProductRegistryReadService server. + """ + return request, metadata + + def post_get_logical_product( + self, response: logical_product.LogicalProduct + ) -> logical_product.LogicalProduct: + """Post-rpc interceptor for get_logical_product + + DEPRECATED. Please use the `post_get_logical_product_with_metadata` + interceptor instead. + + Override in a subclass to read or manipulate the response + after it is returned by the CloudProductRegistryReadService server but before + it is returned to user code. This `post_get_logical_product` interceptor runs + before the `post_get_logical_product_with_metadata` interceptor. + """ + return response + + def post_get_logical_product_with_metadata( + self, + response: logical_product.LogicalProduct, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[logical_product.LogicalProduct, Sequence[Tuple[str, Union[str, bytes]]]]: + """Post-rpc interceptor for get_logical_product + + Override in a subclass to read or manipulate the response or metadata after it + is returned by the CloudProductRegistryReadService server but before it is returned to user code. + + We recommend only using this `post_get_logical_product_with_metadata` + interceptor in new development instead of the `post_get_logical_product` interceptor. + When both interceptors are used, this `post_get_logical_product_with_metadata` interceptor runs after the + `post_get_logical_product` interceptor. The (possibly modified) response returned by + `post_get_logical_product` will be passed to + `post_get_logical_product_with_metadata`. + """ + return response, metadata + + def pre_get_logical_product_variant( + self, + request: cloud_product_registry_read_service.GetLogicalProductVariantRequest, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[ + cloud_product_registry_read_service.GetLogicalProductVariantRequest, + Sequence[Tuple[str, Union[str, bytes]]], + ]: + """Pre-rpc interceptor for get_logical_product_variant + + Override in a subclass to manipulate the request or metadata + before they are sent to the CloudProductRegistryReadService server. + """ + return request, metadata + + def post_get_logical_product_variant( + self, response: logical_product_variant.LogicalProductVariant + ) -> logical_product_variant.LogicalProductVariant: + """Post-rpc interceptor for get_logical_product_variant + + DEPRECATED. Please use the `post_get_logical_product_variant_with_metadata` + interceptor instead. + + Override in a subclass to read or manipulate the response + after it is returned by the CloudProductRegistryReadService server but before + it is returned to user code. This `post_get_logical_product_variant` interceptor runs + before the `post_get_logical_product_variant_with_metadata` interceptor. + """ + return response + + def post_get_logical_product_variant_with_metadata( + self, + response: logical_product_variant.LogicalProductVariant, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[ + logical_product_variant.LogicalProductVariant, + Sequence[Tuple[str, Union[str, bytes]]], + ]: + """Post-rpc interceptor for get_logical_product_variant + + Override in a subclass to read or manipulate the response or metadata after it + is returned by the CloudProductRegistryReadService server but before it is returned to user code. + + We recommend only using this `post_get_logical_product_variant_with_metadata` + interceptor in new development instead of the `post_get_logical_product_variant` interceptor. + When both interceptors are used, this `post_get_logical_product_variant_with_metadata` interceptor runs after the + `post_get_logical_product_variant` interceptor. The (possibly modified) response returned by + `post_get_logical_product_variant` will be passed to + `post_get_logical_product_variant_with_metadata`. + """ + return response, metadata + + def pre_get_product_suite( + self, + request: cloud_product_registry_read_service.GetProductSuiteRequest, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[ + cloud_product_registry_read_service.GetProductSuiteRequest, + Sequence[Tuple[str, Union[str, bytes]]], + ]: + """Pre-rpc interceptor for get_product_suite + + Override in a subclass to manipulate the request or metadata + before they are sent to the CloudProductRegistryReadService server. + """ + return request, metadata + + def post_get_product_suite( + self, response: product_suite.ProductSuite + ) -> product_suite.ProductSuite: + """Post-rpc interceptor for get_product_suite + + DEPRECATED. Please use the `post_get_product_suite_with_metadata` + interceptor instead. + + Override in a subclass to read or manipulate the response + after it is returned by the CloudProductRegistryReadService server but before + it is returned to user code. This `post_get_product_suite` interceptor runs + before the `post_get_product_suite_with_metadata` interceptor. + """ + return response + + def post_get_product_suite_with_metadata( + self, + response: product_suite.ProductSuite, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[product_suite.ProductSuite, Sequence[Tuple[str, Union[str, bytes]]]]: + """Post-rpc interceptor for get_product_suite + + Override in a subclass to read or manipulate the response or metadata after it + is returned by the CloudProductRegistryReadService server but before it is returned to user code. + + We recommend only using this `post_get_product_suite_with_metadata` + interceptor in new development instead of the `post_get_product_suite` interceptor. + When both interceptors are used, this `post_get_product_suite_with_metadata` interceptor runs after the + `post_get_product_suite` interceptor. The (possibly modified) response returned by + `post_get_product_suite` will be passed to + `post_get_product_suite_with_metadata`. + """ + return response, metadata + + def pre_list_logical_products( + self, + request: cloud_product_registry_read_service.ListLogicalProductsRequest, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[ + cloud_product_registry_read_service.ListLogicalProductsRequest, + Sequence[Tuple[str, Union[str, bytes]]], + ]: + """Pre-rpc interceptor for list_logical_products + + Override in a subclass to manipulate the request or metadata + before they are sent to the CloudProductRegistryReadService server. + """ + return request, metadata + + def post_list_logical_products( + self, response: cloud_product_registry_read_service.ListLogicalProductsResponse + ) -> cloud_product_registry_read_service.ListLogicalProductsResponse: + """Post-rpc interceptor for list_logical_products + + DEPRECATED. Please use the `post_list_logical_products_with_metadata` + interceptor instead. + + Override in a subclass to read or manipulate the response + after it is returned by the CloudProductRegistryReadService server but before + it is returned to user code. This `post_list_logical_products` interceptor runs + before the `post_list_logical_products_with_metadata` interceptor. + """ + return response + + def post_list_logical_products_with_metadata( + self, + response: cloud_product_registry_read_service.ListLogicalProductsResponse, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[ + cloud_product_registry_read_service.ListLogicalProductsResponse, + Sequence[Tuple[str, Union[str, bytes]]], + ]: + """Post-rpc interceptor for list_logical_products + + Override in a subclass to read or manipulate the response or metadata after it + is returned by the CloudProductRegistryReadService server but before it is returned to user code. + + We recommend only using this `post_list_logical_products_with_metadata` + interceptor in new development instead of the `post_list_logical_products` interceptor. + When both interceptors are used, this `post_list_logical_products_with_metadata` interceptor runs after the + `post_list_logical_products` interceptor. The (possibly modified) response returned by + `post_list_logical_products` will be passed to + `post_list_logical_products_with_metadata`. + """ + return response, metadata + + def pre_list_logical_product_variants( + self, + request: cloud_product_registry_read_service.ListLogicalProductVariantsRequest, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[ + cloud_product_registry_read_service.ListLogicalProductVariantsRequest, + Sequence[Tuple[str, Union[str, bytes]]], + ]: + """Pre-rpc interceptor for list_logical_product_variants + + Override in a subclass to manipulate the request or metadata + before they are sent to the CloudProductRegistryReadService server. + """ + return request, metadata + + def post_list_logical_product_variants( + self, + response: cloud_product_registry_read_service.ListLogicalProductVariantsResponse, + ) -> cloud_product_registry_read_service.ListLogicalProductVariantsResponse: + """Post-rpc interceptor for list_logical_product_variants + + DEPRECATED. Please use the `post_list_logical_product_variants_with_metadata` + interceptor instead. + + Override in a subclass to read or manipulate the response + after it is returned by the CloudProductRegistryReadService server but before + it is returned to user code. This `post_list_logical_product_variants` interceptor runs + before the `post_list_logical_product_variants_with_metadata` interceptor. + """ + return response + + def post_list_logical_product_variants_with_metadata( + self, + response: cloud_product_registry_read_service.ListLogicalProductVariantsResponse, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[ + cloud_product_registry_read_service.ListLogicalProductVariantsResponse, + Sequence[Tuple[str, Union[str, bytes]]], + ]: + """Post-rpc interceptor for list_logical_product_variants + + Override in a subclass to read or manipulate the response or metadata after it + is returned by the CloudProductRegistryReadService server but before it is returned to user code. + + We recommend only using this `post_list_logical_product_variants_with_metadata` + interceptor in new development instead of the `post_list_logical_product_variants` interceptor. + When both interceptors are used, this `post_list_logical_product_variants_with_metadata` interceptor runs after the + `post_list_logical_product_variants` interceptor. The (possibly modified) response returned by + `post_list_logical_product_variants` will be passed to + `post_list_logical_product_variants_with_metadata`. + """ + return response, metadata + + def pre_list_product_suites( + self, + request: cloud_product_registry_read_service.ListProductSuitesRequest, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[ + cloud_product_registry_read_service.ListProductSuitesRequest, + Sequence[Tuple[str, Union[str, bytes]]], + ]: + """Pre-rpc interceptor for list_product_suites + + Override in a subclass to manipulate the request or metadata + before they are sent to the CloudProductRegistryReadService server. + """ + return request, metadata + + def post_list_product_suites( + self, response: cloud_product_registry_read_service.ListProductSuitesResponse + ) -> cloud_product_registry_read_service.ListProductSuitesResponse: + """Post-rpc interceptor for list_product_suites + + DEPRECATED. Please use the `post_list_product_suites_with_metadata` + interceptor instead. + + Override in a subclass to read or manipulate the response + after it is returned by the CloudProductRegistryReadService server but before + it is returned to user code. This `post_list_product_suites` interceptor runs + before the `post_list_product_suites_with_metadata` interceptor. + """ + return response + + def post_list_product_suites_with_metadata( + self, + response: cloud_product_registry_read_service.ListProductSuitesResponse, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[ + cloud_product_registry_read_service.ListProductSuitesResponse, + Sequence[Tuple[str, Union[str, bytes]]], + ]: + """Post-rpc interceptor for list_product_suites + + Override in a subclass to read or manipulate the response or metadata after it + is returned by the CloudProductRegistryReadService server but before it is returned to user code. + + We recommend only using this `post_list_product_suites_with_metadata` + interceptor in new development instead of the `post_list_product_suites` interceptor. + When both interceptors are used, this `post_list_product_suites_with_metadata` interceptor runs after the + `post_list_product_suites` interceptor. The (possibly modified) response returned by + `post_list_product_suites` will be passed to + `post_list_product_suites_with_metadata`. + """ + return response, metadata + + def pre_lookup_entity( + self, + request: cloud_product_registry_read_service.LookupEntityRequest, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[ + cloud_product_registry_read_service.LookupEntityRequest, + Sequence[Tuple[str, Union[str, bytes]]], + ]: + """Pre-rpc interceptor for lookup_entity + + Override in a subclass to manipulate the request or metadata + before they are sent to the CloudProductRegistryReadService server. + """ + return request, metadata + + def post_lookup_entity( + self, response: cloud_product_registry_read_service.LookupEntityResponse + ) -> cloud_product_registry_read_service.LookupEntityResponse: + """Post-rpc interceptor for lookup_entity + + DEPRECATED. Please use the `post_lookup_entity_with_metadata` + interceptor instead. + + Override in a subclass to read or manipulate the response + after it is returned by the CloudProductRegistryReadService server but before + it is returned to user code. This `post_lookup_entity` interceptor runs + before the `post_lookup_entity_with_metadata` interceptor. + """ + return response + + def post_lookup_entity_with_metadata( + self, + response: cloud_product_registry_read_service.LookupEntityResponse, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[ + cloud_product_registry_read_service.LookupEntityResponse, + Sequence[Tuple[str, Union[str, bytes]]], + ]: + """Post-rpc interceptor for lookup_entity + + Override in a subclass to read or manipulate the response or metadata after it + is returned by the CloudProductRegistryReadService server but before it is returned to user code. + + We recommend only using this `post_lookup_entity_with_metadata` + interceptor in new development instead of the `post_lookup_entity` interceptor. + When both interceptors are used, this `post_lookup_entity_with_metadata` interceptor runs after the + `post_lookup_entity` interceptor. The (possibly modified) response returned by + `post_lookup_entity` will be passed to + `post_lookup_entity_with_metadata`. + """ + return response, metadata + + +@dataclasses.dataclass +class CloudProductRegistryReadServiceRestStub: + _session: AuthorizedSession + _host: str + _interceptor: CloudProductRegistryReadServiceRestInterceptor + + +class CloudProductRegistryReadServiceRestTransport( + _BaseCloudProductRegistryReadServiceRestTransport +): + """REST backend synchronous transport for CloudProductRegistryReadService. + + Cloud Product Registry Read Service provides capabilities to + access all first and third party Google Cloud products. + + 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 = "cloudproductregistry.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[CloudProductRegistryReadServiceRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to (default: 'cloudproductregistry.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]): Deprecated. A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. This argument will be + removed in the next major version of this library. + 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. + interceptor (Optional[CloudProductRegistryReadServiceRestInterceptor]): Interceptor used + to manipulate requests, request metadata, and responses. + api_audience (Optional[str]): The intended audience for the API calls + to the service that will be set when using certain 3rd party + authentication flows. Audience is typically a resource identifier. + If not set, the host value will be used as a default. + """ + # 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 CloudProductRegistryReadServiceRestInterceptor() + ) + self._prep_wrapped_messages(client_info) + + class _GetLogicalProduct( + _BaseCloudProductRegistryReadServiceRestTransport._BaseGetLogicalProduct, + CloudProductRegistryReadServiceRestStub, + ): + def __hash__(self): + return hash( + "CloudProductRegistryReadServiceRestTransport.GetLogicalProduct" + ) + + @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: cloud_product_registry_read_service.GetLogicalProductRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> logical_product.LogicalProduct: + r"""Call the get logical product method over HTTP. + + Args: + request (~.cloud_product_registry_read_service.GetLogicalProductRequest): + The request object. Request message for + GetLogicalProduct. + 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: + ~.logical_product.LogicalProduct: + Represents an independent service + offering that can be provisioned by a + customer. + + """ + + http_options = _BaseCloudProductRegistryReadServiceRestTransport._BaseGetLogicalProduct._get_http_options() + + request, metadata = self._interceptor.pre_get_logical_product( + request, metadata + ) + transcoded_request = _BaseCloudProductRegistryReadServiceRestTransport._BaseGetLogicalProduct._get_transcoded_request( + http_options, request + ) + + # Jsonify the query params + query_params = _BaseCloudProductRegistryReadServiceRestTransport._BaseGetLogicalProduct._get_query_params_json( + transcoded_request + ) + + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + request_url = "{host}{uri}".format( + host=self._host, uri=transcoded_request["uri"] + ) + method = transcoded_request["method"] + try: + request_payload = type(request).to_json(request) + except: + request_payload = None + http_request = { + "payload": request_payload, + "requestMethod": method, + "requestUrl": request_url, + "headers": dict(metadata), + } + _LOGGER.debug( + f"Sending request for google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.GetLogicalProduct", + extra={ + "serviceName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "rpcName": "GetLogicalProduct", + "httpRequest": http_request, + "metadata": http_request["headers"], + }, + ) + + # Send the request + response = CloudProductRegistryReadServiceRestTransport._GetLogicalProduct._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 = logical_product.LogicalProduct() + pb_resp = logical_product.LogicalProduct.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + + resp = self._interceptor.post_get_logical_product(resp) + response_metadata = [(k, str(v)) for k, v in response.headers.items()] + resp, _ = self._interceptor.post_get_logical_product_with_metadata( + resp, response_metadata + ) + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + try: + response_payload = logical_product.LogicalProduct.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.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.get_logical_product", + extra={ + "serviceName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "rpcName": "GetLogicalProduct", + "metadata": http_response["headers"], + "httpResponse": http_response, + }, + ) + return resp + + class _GetLogicalProductVariant( + _BaseCloudProductRegistryReadServiceRestTransport._BaseGetLogicalProductVariant, + CloudProductRegistryReadServiceRestStub, + ): + def __hash__(self): + return hash( + "CloudProductRegistryReadServiceRestTransport.GetLogicalProductVariant" + ) + + @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: cloud_product_registry_read_service.GetLogicalProductVariantRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> logical_product_variant.LogicalProductVariant: + r"""Call the get logical product + variant method over HTTP. + + Args: + request (~.cloud_product_registry_read_service.GetLogicalProductVariantRequest): + The request object. Request message for + GetLogicalProductVariant. + 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: + ~.logical_product_variant.LogicalProductVariant: + Represents a distinct offering + derived from a primary product that + retains core functionalities but offers + specialized features for a specific + market segment. + + """ + + http_options = _BaseCloudProductRegistryReadServiceRestTransport._BaseGetLogicalProductVariant._get_http_options() + + request, metadata = self._interceptor.pre_get_logical_product_variant( + request, metadata + ) + transcoded_request = _BaseCloudProductRegistryReadServiceRestTransport._BaseGetLogicalProductVariant._get_transcoded_request( + http_options, request + ) + + # Jsonify the query params + query_params = _BaseCloudProductRegistryReadServiceRestTransport._BaseGetLogicalProductVariant._get_query_params_json( + transcoded_request + ) + + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + request_url = "{host}{uri}".format( + host=self._host, uri=transcoded_request["uri"] + ) + method = transcoded_request["method"] + try: + request_payload = type(request).to_json(request) + except: + request_payload = None + http_request = { + "payload": request_payload, + "requestMethod": method, + "requestUrl": request_url, + "headers": dict(metadata), + } + _LOGGER.debug( + f"Sending request for google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.GetLogicalProductVariant", + extra={ + "serviceName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "rpcName": "GetLogicalProductVariant", + "httpRequest": http_request, + "metadata": http_request["headers"], + }, + ) + + # Send the request + response = CloudProductRegistryReadServiceRestTransport._GetLogicalProductVariant._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 = logical_product_variant.LogicalProductVariant() + pb_resp = logical_product_variant.LogicalProductVariant.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + + resp = self._interceptor.post_get_logical_product_variant(resp) + response_metadata = [(k, str(v)) for k, v in response.headers.items()] + resp, _ = self._interceptor.post_get_logical_product_variant_with_metadata( + resp, response_metadata + ) + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + try: + response_payload = ( + logical_product_variant.LogicalProductVariant.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.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.get_logical_product_variant", + extra={ + "serviceName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "rpcName": "GetLogicalProductVariant", + "metadata": http_response["headers"], + "httpResponse": http_response, + }, + ) + return resp + + class _GetProductSuite( + _BaseCloudProductRegistryReadServiceRestTransport._BaseGetProductSuite, + CloudProductRegistryReadServiceRestStub, + ): + def __hash__(self): + return hash("CloudProductRegistryReadServiceRestTransport.GetProductSuite") + + @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: cloud_product_registry_read_service.GetProductSuiteRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> product_suite.ProductSuite: + r"""Call the get product suite method over HTTP. + + Args: + request (~.cloud_product_registry_read_service.GetProductSuiteRequest): + The request object. Request message for GetProductSuite. + 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: + ~.product_suite.ProductSuite: + Represents a unified grouping of + products sharing a common brand and + market positioning. + + """ + + http_options = _BaseCloudProductRegistryReadServiceRestTransport._BaseGetProductSuite._get_http_options() + + request, metadata = self._interceptor.pre_get_product_suite( + request, metadata + ) + transcoded_request = _BaseCloudProductRegistryReadServiceRestTransport._BaseGetProductSuite._get_transcoded_request( + http_options, request + ) + + # Jsonify the query params + query_params = _BaseCloudProductRegistryReadServiceRestTransport._BaseGetProductSuite._get_query_params_json( + transcoded_request + ) + + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + request_url = "{host}{uri}".format( + host=self._host, uri=transcoded_request["uri"] + ) + method = transcoded_request["method"] + try: + request_payload = type(request).to_json(request) + except: + request_payload = None + http_request = { + "payload": request_payload, + "requestMethod": method, + "requestUrl": request_url, + "headers": dict(metadata), + } + _LOGGER.debug( + f"Sending request for google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.GetProductSuite", + extra={ + "serviceName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "rpcName": "GetProductSuite", + "httpRequest": http_request, + "metadata": http_request["headers"], + }, + ) + + # Send the request + response = CloudProductRegistryReadServiceRestTransport._GetProductSuite._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 = product_suite.ProductSuite() + pb_resp = product_suite.ProductSuite.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + + resp = self._interceptor.post_get_product_suite(resp) + response_metadata = [(k, str(v)) for k, v in response.headers.items()] + resp, _ = self._interceptor.post_get_product_suite_with_metadata( + resp, response_metadata + ) + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + try: + response_payload = product_suite.ProductSuite.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.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.get_product_suite", + extra={ + "serviceName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "rpcName": "GetProductSuite", + "metadata": http_response["headers"], + "httpResponse": http_response, + }, + ) + return resp + + class _ListLogicalProducts( + _BaseCloudProductRegistryReadServiceRestTransport._BaseListLogicalProducts, + CloudProductRegistryReadServiceRestStub, + ): + def __hash__(self): + return hash( + "CloudProductRegistryReadServiceRestTransport.ListLogicalProducts" + ) + + @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: cloud_product_registry_read_service.ListLogicalProductsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> cloud_product_registry_read_service.ListLogicalProductsResponse: + r"""Call the list logical products method over HTTP. + + Args: + request (~.cloud_product_registry_read_service.ListLogicalProductsRequest): + The request object. Request message for + ListLogicalProducts. + 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: + ~.cloud_product_registry_read_service.ListLogicalProductsResponse: + Response message for + ListLogicalProducts. + + """ + + http_options = _BaseCloudProductRegistryReadServiceRestTransport._BaseListLogicalProducts._get_http_options() + + request, metadata = self._interceptor.pre_list_logical_products( + request, metadata + ) + transcoded_request = _BaseCloudProductRegistryReadServiceRestTransport._BaseListLogicalProducts._get_transcoded_request( + http_options, request + ) + + # Jsonify the query params + query_params = _BaseCloudProductRegistryReadServiceRestTransport._BaseListLogicalProducts._get_query_params_json( + transcoded_request + ) + + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + request_url = "{host}{uri}".format( + host=self._host, uri=transcoded_request["uri"] + ) + method = transcoded_request["method"] + try: + request_payload = type(request).to_json(request) + except: + request_payload = None + http_request = { + "payload": request_payload, + "requestMethod": method, + "requestUrl": request_url, + "headers": dict(metadata), + } + _LOGGER.debug( + f"Sending request for google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.ListLogicalProducts", + extra={ + "serviceName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "rpcName": "ListLogicalProducts", + "httpRequest": http_request, + "metadata": http_request["headers"], + }, + ) + + # Send the request + response = CloudProductRegistryReadServiceRestTransport._ListLogicalProducts._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 = cloud_product_registry_read_service.ListLogicalProductsResponse() + pb_resp = ( + cloud_product_registry_read_service.ListLogicalProductsResponse.pb(resp) + ) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + + resp = self._interceptor.post_list_logical_products(resp) + response_metadata = [(k, str(v)) for k, v in response.headers.items()] + resp, _ = self._interceptor.post_list_logical_products_with_metadata( + resp, response_metadata + ) + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + try: + response_payload = cloud_product_registry_read_service.ListLogicalProductsResponse.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.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.list_logical_products", + extra={ + "serviceName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "rpcName": "ListLogicalProducts", + "metadata": http_response["headers"], + "httpResponse": http_response, + }, + ) + return resp + + class _ListLogicalProductVariants( + _BaseCloudProductRegistryReadServiceRestTransport._BaseListLogicalProductVariants, + CloudProductRegistryReadServiceRestStub, + ): + def __hash__(self): + return hash( + "CloudProductRegistryReadServiceRestTransport.ListLogicalProductVariants" + ) + + @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: cloud_product_registry_read_service.ListLogicalProductVariantsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> cloud_product_registry_read_service.ListLogicalProductVariantsResponse: + r"""Call the list logical product + variants method over HTTP. + + Args: + request (~.cloud_product_registry_read_service.ListLogicalProductVariantsRequest): + The request object. Request message for + ListLogicalProductVariants. + 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: + ~.cloud_product_registry_read_service.ListLogicalProductVariantsResponse: + Response message for + ListLogicalProductVariants. + + """ + + http_options = _BaseCloudProductRegistryReadServiceRestTransport._BaseListLogicalProductVariants._get_http_options() + + request, metadata = self._interceptor.pre_list_logical_product_variants( + request, metadata + ) + transcoded_request = _BaseCloudProductRegistryReadServiceRestTransport._BaseListLogicalProductVariants._get_transcoded_request( + http_options, request + ) + + # Jsonify the query params + query_params = _BaseCloudProductRegistryReadServiceRestTransport._BaseListLogicalProductVariants._get_query_params_json( + transcoded_request + ) + + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + request_url = "{host}{uri}".format( + host=self._host, uri=transcoded_request["uri"] + ) + method = transcoded_request["method"] + try: + request_payload = type(request).to_json(request) + except: + request_payload = None + http_request = { + "payload": request_payload, + "requestMethod": method, + "requestUrl": request_url, + "headers": dict(metadata), + } + _LOGGER.debug( + f"Sending request for google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.ListLogicalProductVariants", + extra={ + "serviceName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "rpcName": "ListLogicalProductVariants", + "httpRequest": http_request, + "metadata": http_request["headers"], + }, + ) + + # Send the request + response = CloudProductRegistryReadServiceRestTransport._ListLogicalProductVariants._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 = ( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse() + ) + pb_resp = cloud_product_registry_read_service.ListLogicalProductVariantsResponse.pb( + resp + ) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + + resp = self._interceptor.post_list_logical_product_variants(resp) + response_metadata = [(k, str(v)) for k, v in response.headers.items()] + resp, _ = ( + self._interceptor.post_list_logical_product_variants_with_metadata( + resp, response_metadata + ) + ) + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + try: + response_payload = cloud_product_registry_read_service.ListLogicalProductVariantsResponse.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.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.list_logical_product_variants", + extra={ + "serviceName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "rpcName": "ListLogicalProductVariants", + "metadata": http_response["headers"], + "httpResponse": http_response, + }, + ) + return resp + + class _ListProductSuites( + _BaseCloudProductRegistryReadServiceRestTransport._BaseListProductSuites, + CloudProductRegistryReadServiceRestStub, + ): + def __hash__(self): + return hash( + "CloudProductRegistryReadServiceRestTransport.ListProductSuites" + ) + + @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: cloud_product_registry_read_service.ListProductSuitesRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> cloud_product_registry_read_service.ListProductSuitesResponse: + r"""Call the list product suites method over HTTP. + + Args: + request (~.cloud_product_registry_read_service.ListProductSuitesRequest): + The request object. Request message for + ListProductSuites. + 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: + ~.cloud_product_registry_read_service.ListProductSuitesResponse: + Response message for + ListProductSuites. + + """ + + http_options = _BaseCloudProductRegistryReadServiceRestTransport._BaseListProductSuites._get_http_options() + + request, metadata = self._interceptor.pre_list_product_suites( + request, metadata + ) + transcoded_request = _BaseCloudProductRegistryReadServiceRestTransport._BaseListProductSuites._get_transcoded_request( + http_options, request + ) + + # Jsonify the query params + query_params = _BaseCloudProductRegistryReadServiceRestTransport._BaseListProductSuites._get_query_params_json( + transcoded_request + ) + + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + request_url = "{host}{uri}".format( + host=self._host, uri=transcoded_request["uri"] + ) + method = transcoded_request["method"] + try: + request_payload = type(request).to_json(request) + except: + request_payload = None + http_request = { + "payload": request_payload, + "requestMethod": method, + "requestUrl": request_url, + "headers": dict(metadata), + } + _LOGGER.debug( + f"Sending request for google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.ListProductSuites", + extra={ + "serviceName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "rpcName": "ListProductSuites", + "httpRequest": http_request, + "metadata": http_request["headers"], + }, + ) + + # Send the request + response = CloudProductRegistryReadServiceRestTransport._ListProductSuites._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 = cloud_product_registry_read_service.ListProductSuitesResponse() + pb_resp = cloud_product_registry_read_service.ListProductSuitesResponse.pb( + resp + ) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + + resp = self._interceptor.post_list_product_suites(resp) + response_metadata = [(k, str(v)) for k, v in response.headers.items()] + resp, _ = self._interceptor.post_list_product_suites_with_metadata( + resp, response_metadata + ) + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + try: + response_payload = cloud_product_registry_read_service.ListProductSuitesResponse.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.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.list_product_suites", + extra={ + "serviceName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "rpcName": "ListProductSuites", + "metadata": http_response["headers"], + "httpResponse": http_response, + }, + ) + return resp + + class _LookupEntity( + _BaseCloudProductRegistryReadServiceRestTransport._BaseLookupEntity, + CloudProductRegistryReadServiceRestStub, + ): + def __hash__(self): + return hash("CloudProductRegistryReadServiceRestTransport.LookupEntity") + + @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: cloud_product_registry_read_service.LookupEntityRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> cloud_product_registry_read_service.LookupEntityResponse: + r"""Call the lookup entity method over HTTP. + + Args: + request (~.cloud_product_registry_read_service.LookupEntityRequest): + The request object. Request message for LookupEntity. + 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: + ~.cloud_product_registry_read_service.LookupEntityResponse: + Response message for LookupEntity. + """ + + http_options = _BaseCloudProductRegistryReadServiceRestTransport._BaseLookupEntity._get_http_options() + + request, metadata = self._interceptor.pre_lookup_entity(request, metadata) + transcoded_request = _BaseCloudProductRegistryReadServiceRestTransport._BaseLookupEntity._get_transcoded_request( + http_options, request + ) + + # Jsonify the query params + query_params = _BaseCloudProductRegistryReadServiceRestTransport._BaseLookupEntity._get_query_params_json( + transcoded_request + ) + + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + request_url = "{host}{uri}".format( + host=self._host, uri=transcoded_request["uri"] + ) + method = transcoded_request["method"] + try: + request_payload = type(request).to_json(request) + except: + request_payload = None + http_request = { + "payload": request_payload, + "requestMethod": method, + "requestUrl": request_url, + "headers": dict(metadata), + } + _LOGGER.debug( + f"Sending request for google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.LookupEntity", + extra={ + "serviceName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "rpcName": "LookupEntity", + "httpRequest": http_request, + "metadata": http_request["headers"], + }, + ) + + # Send the request + response = CloudProductRegistryReadServiceRestTransport._LookupEntity._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 = cloud_product_registry_read_service.LookupEntityResponse() + pb_resp = cloud_product_registry_read_service.LookupEntityResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + + resp = self._interceptor.post_lookup_entity(resp) + response_metadata = [(k, str(v)) for k, v in response.headers.items()] + resp, _ = self._interceptor.post_lookup_entity_with_metadata( + resp, response_metadata + ) + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + try: + response_payload = cloud_product_registry_read_service.LookupEntityResponse.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.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.lookup_entity", + extra={ + "serviceName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "rpcName": "LookupEntity", + "metadata": http_response["headers"], + "httpResponse": http_response, + }, + ) + return resp + + @property + def get_logical_product( + self, + ) -> Callable[ + [cloud_product_registry_read_service.GetLogicalProductRequest], + logical_product.LogicalProduct, + ]: + # 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._GetLogicalProduct(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_logical_product_variant( + self, + ) -> Callable[ + [cloud_product_registry_read_service.GetLogicalProductVariantRequest], + logical_product_variant.LogicalProductVariant, + ]: + # 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._GetLogicalProductVariant( + self._session, self._host, self._interceptor + ) # type: ignore + + @property + def get_product_suite( + self, + ) -> Callable[ + [cloud_product_registry_read_service.GetProductSuiteRequest], + product_suite.ProductSuite, + ]: + # 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._GetProductSuite(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_logical_products( + self, + ) -> Callable[ + [cloud_product_registry_read_service.ListLogicalProductsRequest], + cloud_product_registry_read_service.ListLogicalProductsResponse, + ]: + # 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._ListLogicalProducts(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_logical_product_variants( + self, + ) -> Callable[ + [cloud_product_registry_read_service.ListLogicalProductVariantsRequest], + cloud_product_registry_read_service.ListLogicalProductVariantsResponse, + ]: + # 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._ListLogicalProductVariants( + self._session, self._host, self._interceptor + ) # type: ignore + + @property + def list_product_suites( + self, + ) -> Callable[ + [cloud_product_registry_read_service.ListProductSuitesRequest], + cloud_product_registry_read_service.ListProductSuitesResponse, + ]: + # 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._ListProductSuites(self._session, self._host, self._interceptor) # type: ignore + + @property + def lookup_entity( + self, + ) -> Callable[ + [cloud_product_registry_read_service.LookupEntityRequest], + cloud_product_registry_read_service.LookupEntityResponse, + ]: + # 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._LookupEntity(self._session, self._host, self._interceptor) # type: ignore + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("CloudProductRegistryReadServiceRestTransport",) diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/rest_base.py b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/rest_base.py new file mode 100644 index 000000000000..24253a269e9d --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/services/cloud_product_registry_read_service/transports/rest_base.py @@ -0,0 +1,423 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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.cloud.productregistry_v1.types import ( + cloud_product_registry_read_service, + logical_product, + logical_product_variant, + product_suite, +) + +from .base import DEFAULT_CLIENT_INFO, CloudProductRegistryReadServiceTransport + + +class _BaseCloudProductRegistryReadServiceRestTransport( + CloudProductRegistryReadServiceTransport +): + """Base REST backend transport for CloudProductRegistryReadService. + + 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 = "cloudproductregistry.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: 'cloudproductregistry.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 _BaseGetLogicalProduct: + 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": "/v1/{name=logicalProducts/*}", + }, + ] + return http_options + + @staticmethod + def _get_transcoded_request(http_options, request): + pb_request = ( + cloud_product_registry_read_service.GetLogicalProductRequest.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( + _BaseCloudProductRegistryReadServiceRestTransport._BaseGetLogicalProduct._get_unset_required_fields( + query_params + ) + ) + + query_params["$alt"] = "json;enum-encoding=int" + return query_params + + class _BaseGetLogicalProductVariant: + 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": "/v1/{name=logicalProducts/*/variants/*}", + }, + ] + return http_options + + @staticmethod + def _get_transcoded_request(http_options, request): + pb_request = ( + cloud_product_registry_read_service.GetLogicalProductVariantRequest.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( + _BaseCloudProductRegistryReadServiceRestTransport._BaseGetLogicalProductVariant._get_unset_required_fields( + query_params + ) + ) + + query_params["$alt"] = "json;enum-encoding=int" + return query_params + + class _BaseGetProductSuite: + 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": "/v1/{name=productSuites/*}", + }, + ] + return http_options + + @staticmethod + def _get_transcoded_request(http_options, request): + pb_request = cloud_product_registry_read_service.GetProductSuiteRequest.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( + _BaseCloudProductRegistryReadServiceRestTransport._BaseGetProductSuite._get_unset_required_fields( + query_params + ) + ) + + query_params["$alt"] = "json;enum-encoding=int" + return query_params + + class _BaseListLogicalProducts: + def __hash__(self): # pragma: NO COVER + return NotImplementedError("__hash__ must be implemented.") + + @staticmethod + def _get_http_options(): + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v1/logicalProducts", + }, + ] + return http_options + + @staticmethod + def _get_transcoded_request(http_options, request): + pb_request = ( + cloud_product_registry_read_service.ListLogicalProductsRequest.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["$alt"] = "json;enum-encoding=int" + return query_params + + class _BaseListLogicalProductVariants: + 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": "/v1/{parent=logicalProducts/*}/variants", + }, + ] + return http_options + + @staticmethod + def _get_transcoded_request(http_options, request): + pb_request = cloud_product_registry_read_service.ListLogicalProductVariantsRequest.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( + _BaseCloudProductRegistryReadServiceRestTransport._BaseListLogicalProductVariants._get_unset_required_fields( + query_params + ) + ) + + query_params["$alt"] = "json;enum-encoding=int" + return query_params + + class _BaseListProductSuites: + def __hash__(self): # pragma: NO COVER + return NotImplementedError("__hash__ must be implemented.") + + @staticmethod + def _get_http_options(): + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v1/productSuites", + }, + ] + return http_options + + @staticmethod + def _get_transcoded_request(http_options, request): + pb_request = ( + cloud_product_registry_read_service.ListProductSuitesRequest.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["$alt"] = "json;enum-encoding=int" + return query_params + + class _BaseLookupEntity: + 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": "/v1/{lookup_uri=logicalProducts/*}:lookupEntity", + }, + { + "method": "get", + "uri": "/v1/{lookup_uri=logicalProducts/*/variants/*}:lookupEntity", + }, + { + "method": "get", + "uri": "/v1/{lookup_uri=productSuites/*}:lookupEntity", + }, + ] + return http_options + + @staticmethod + def _get_transcoded_request(http_options, request): + pb_request = cloud_product_registry_read_service.LookupEntityRequest.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( + _BaseCloudProductRegistryReadServiceRestTransport._BaseLookupEntity._get_unset_required_fields( + query_params + ) + ) + + query_params["$alt"] = "json;enum-encoding=int" + return query_params + + +__all__ = ("_BaseCloudProductRegistryReadServiceRestTransport",) diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/types/__init__.py b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/types/__init__.py new file mode 100644 index 000000000000..13b3b18ce987 --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/types/__init__.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 .cloud_product_registry_read_service import ( + GetLogicalProductRequest, + GetLogicalProductVariantRequest, + GetProductSuiteRequest, + ListLogicalProductsRequest, + ListLogicalProductsResponse, + ListLogicalProductVariantsRequest, + ListLogicalProductVariantsResponse, + ListProductSuitesRequest, + ListProductSuitesResponse, + LookupEntityRequest, + LookupEntityResponse, +) +from .lifecycle_state import ( + LifecycleState, +) +from .logical_product import ( + LogicalProduct, +) +from .logical_product_variant import ( + LogicalProductVariant, +) +from .product_suite import ( + ProductSuite, +) + +__all__ = ( + "GetLogicalProductRequest", + "GetLogicalProductVariantRequest", + "GetProductSuiteRequest", + "ListLogicalProductsRequest", + "ListLogicalProductsResponse", + "ListLogicalProductVariantsRequest", + "ListLogicalProductVariantsResponse", + "ListProductSuitesRequest", + "ListProductSuitesResponse", + "LookupEntityRequest", + "LookupEntityResponse", + "LifecycleState", + "LogicalProduct", + "LogicalProductVariant", + "ProductSuite", +) diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/types/cloud_product_registry_read_service.py b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/types/cloud_product_registry_read_service.py new file mode 100644 index 000000000000..eef928777db5 --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/types/cloud_product_registry_read_service.py @@ -0,0 +1,349 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import MutableMapping, MutableSequence + +import proto # type: ignore + +from google.cloud.productregistry_v1.types import logical_product as gcp_logical_product +from google.cloud.productregistry_v1.types import ( + logical_product_variant as gcp_logical_product_variant, +) +from google.cloud.productregistry_v1.types import product_suite as gcp_product_suite + +__protobuf__ = proto.module( + package="google.cloud.productregistry.v1", + manifest={ + "GetProductSuiteRequest", + "GetLogicalProductRequest", + "GetLogicalProductVariantRequest", + "ListProductSuitesRequest", + "ListProductSuitesResponse", + "ListLogicalProductsRequest", + "ListLogicalProductsResponse", + "ListLogicalProductVariantsRequest", + "ListLogicalProductVariantsResponse", + "LookupEntityRequest", + "LookupEntityResponse", + }, +) + + +class GetProductSuiteRequest(proto.Message): + r"""Request message for GetProductSuite. + + Attributes: + name (str): + Required. The name of the ProductSuite to retrieve. Format: + productSuites/{product_suite} + """ + + name: str = proto.Field( + proto.STRING, + number=1, + ) + + +class GetLogicalProductRequest(proto.Message): + r"""Request message for GetLogicalProduct. + + Attributes: + name (str): + Required. The name of the LogicalProduct to retrieve. + Format: logicalProducts/{logical_product} + """ + + name: str = proto.Field( + proto.STRING, + number=1, + ) + + +class GetLogicalProductVariantRequest(proto.Message): + r"""Request message for GetLogicalProductVariant. + + Attributes: + name (str): + Required. The name of the LogicalProductVariant to retrieve. + Format: logicalProducts/{logical_product}/variants/{variant} + """ + + name: str = proto.Field( + proto.STRING, + number=1, + ) + + +class ListProductSuitesRequest(proto.Message): + r"""Request message for ListProductSuites. + + Attributes: + page_size (int): + Optional. The maximum number of suites to + return. The service may return fewer than this + value. If unspecified, at most 100 suites will + be returned. The maximum value is 500; values + above 500 will be coerced to 500. + page_token (str): + Optional. A page token, received from a previous + ``ListProductSuites`` call. Provide this to retrieve the + subsequent page. + + When paginating, all other parameters provided to + ``ListProductSuites`` must match the call that provided the + page token. + """ + + page_size: int = proto.Field( + proto.INT32, + number=1, + ) + page_token: str = proto.Field( + proto.STRING, + number=2, + ) + + +class ListProductSuitesResponse(proto.Message): + r"""Response message for ListProductSuites. + + Attributes: + product_suites (MutableSequence[google.cloud.productregistry_v1.types.ProductSuite]): + Matched ProductSuites + 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 + + product_suites: MutableSequence[gcp_product_suite.ProductSuite] = ( + proto.RepeatedField( + proto.MESSAGE, + number=1, + message=gcp_product_suite.ProductSuite, + ) + ) + next_page_token: str = proto.Field( + proto.STRING, + number=2, + ) + + +class ListLogicalProductsRequest(proto.Message): + r"""Request message for ListLogicalProducts. + + Attributes: + filter (str): + Optional. The filter expression for listing logical + products. Filter syntax: https://google.aip.dev/160 + Supported fields: suite_id + page_size (int): + Optional. The maximum number of logical + products to return. The service may return fewer + than this value. If unspecified, at most 100 + logical products will be returned. The maximum + value is 500; values above 500 will be coerced + to 500. + page_token (str): + Optional. A page token, received from a previous + ``ListLogicalProducts`` call. Provide this to retrieve the + subsequent page. + + When paginating, all other parameters provided to + ``ListLogicalProducts`` must match the call that provided + the page token. + """ + + filter: 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 ListLogicalProductsResponse(proto.Message): + r"""Response message for ListLogicalProducts. + + Attributes: + logical_products (MutableSequence[google.cloud.productregistry_v1.types.LogicalProduct]): + Matched LogicalProducts + 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 + + logical_products: MutableSequence[gcp_logical_product.LogicalProduct] = ( + proto.RepeatedField( + proto.MESSAGE, + number=1, + message=gcp_logical_product.LogicalProduct, + ) + ) + next_page_token: str = proto.Field( + proto.STRING, + number=2, + ) + + +class ListLogicalProductVariantsRequest(proto.Message): + r"""Request message for ListLogicalProductVariants. + + Attributes: + parent (str): + Required. Parent logical product id. Format: + logicalProducts/{logical_product} + page_size (int): + Optional. The maximum number of logical + product variants to return. The service may + return fewer than this value. If unspecified, at + most 100 logical product variants will be + returned. The maximum value is 500; values above + 500 will be coerced to 500. + page_token (str): + Optional. A page token, received from a previous + ``ListLogicalProductVariants`` call. Provide this to + retrieve the subsequent page. + + When paginating, all other parameters provided to + ``ListLogicalProductVariants`` 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 ListLogicalProductVariantsResponse(proto.Message): + r"""Response message for ListLogicalProductVariants. + + Attributes: + logical_product_variants (MutableSequence[google.cloud.productregistry_v1.types.LogicalProductVariant]): + Matched LogicalProductVariants + 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 + + logical_product_variants: MutableSequence[ + gcp_logical_product_variant.LogicalProductVariant + ] = proto.RepeatedField( + proto.MESSAGE, + number=1, + message=gcp_logical_product_variant.LogicalProductVariant, + ) + next_page_token: str = proto.Field( + proto.STRING, + number=2, + ) + + +class LookupEntityRequest(proto.Message): + r"""Request message for LookupEntity. + + Attributes: + lookup_uri (str): + Required. Entity uri to look up. Supported Formats: + logicalProducts/{logical_product} + logicalProducts/{logical_product}/variants/{variant} + productSuites/{product_suite} + """ + + lookup_uri: str = proto.Field( + proto.STRING, + number=1, + ) + + +class LookupEntityResponse(proto.Message): + r"""Response message for LookupEntity. + + 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: + logical_product (google.cloud.productregistry_v1.types.LogicalProduct): + Matched LogicalProduct. + + This field is a member of `oneof`_ ``entity``. + logical_product_variant (google.cloud.productregistry_v1.types.LogicalProductVariant): + Matched LogicalProductVariant. + + This field is a member of `oneof`_ ``entity``. + product_suite (google.cloud.productregistry_v1.types.ProductSuite): + Matched ProductSuite. + + This field is a member of `oneof`_ ``entity``. + """ + + logical_product: gcp_logical_product.LogicalProduct = proto.Field( + proto.MESSAGE, + number=1, + oneof="entity", + message=gcp_logical_product.LogicalProduct, + ) + logical_product_variant: gcp_logical_product_variant.LogicalProductVariant = ( + proto.Field( + proto.MESSAGE, + number=2, + oneof="entity", + message=gcp_logical_product_variant.LogicalProductVariant, + ) + ) + product_suite: gcp_product_suite.ProductSuite = proto.Field( + proto.MESSAGE, + number=3, + oneof="entity", + message=gcp_product_suite.ProductSuite, + ) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/types/lifecycle_state.py b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/types/lifecycle_state.py new file mode 100644 index 000000000000..ff316afe1c03 --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/types/lifecycle_state.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import MutableMapping, MutableSequence + +import proto # type: ignore + +__protobuf__ = proto.module( + package="google.cloud.productregistry.v1", + manifest={ + "LifecycleState", + }, +) + + +class LifecycleState(proto.Enum): + r"""Enum representing the lifecycle state of the underlying + entity. + + Values: + LIFECYCLE_STATE_UNSPECIFIED (0): + The default value. This value is used if the + lifecycle state is not set. + LIFECYCLE_STATE_PUBLIC_PREVIEW (1): + The entity is in Public Preview. It is + available to all customers, but may not be + feature-complete or have full support + guarantees. + LIFECYCLE_STATE_PRIVATE_GA (2): + The entity is in Private General + Availability. It is fully supported and stable, + but only available to a select group of + customers. + LIFECYCLE_STATE_GA (3): + The entity is Generally Available. It is + fully supported, stable, and available to all + customers. + LIFECYCLE_STATE_DEPRECATED (4): + The entity is deprecated. It is no longer + recommended for use and may be removed in a + future version. + """ + + LIFECYCLE_STATE_UNSPECIFIED = 0 + LIFECYCLE_STATE_PUBLIC_PREVIEW = 1 + LIFECYCLE_STATE_PRIVATE_GA = 2 + LIFECYCLE_STATE_GA = 3 + LIFECYCLE_STATE_DEPRECATED = 4 + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/types/logical_product.py b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/types/logical_product.py new file mode 100644 index 000000000000..3f92ee47df00 --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/types/logical_product.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import MutableMapping, MutableSequence + +import proto # type: ignore + +from google.cloud.productregistry_v1.types import lifecycle_state as gcp_lifecycle_state + +__protobuf__ = proto.module( + package="google.cloud.productregistry.v1", + manifest={ + "LogicalProduct", + }, +) + + +class LogicalProduct(proto.Message): + r"""Represents an independent service offering that can be + provisioned by a customer. + + Attributes: + name (str): + Identifier. The resource name of the LogicalProduct. Format: + logicalProducts/{logical_product}. + title (str): + Display name of the LogicalProduct. + product_suite (str): + Product suite associated with the logical product. Format: + productSuites/{product_suite}. + variants (MutableSequence[str]): + Output only. Child variant resource references. Format: + logicalProducts/{logical_product}/variants/{variant} + lifecycle_state (google.cloud.productregistry_v1.types.LifecycleState): + Output only. Current Lifecycle state of the + logical product. + replaced (bool): + Output only. Indicates whether the logical product has been + replaced. If ``false``, the product is active. If ``true``, + the product has been replaced by another type, and the + ``replacement`` field contains the resource name of that + replacement. + replacement (str): + Output only. The resource name of the Logical Entity that + the logical product is replaced by. This field is only + populated when this logical product is replaced by some + other type. Eg: + logicalProducts/{logical_product}/variants/{variant}, + productSuites/{product_suite}, etc. + """ + + name: str = proto.Field( + proto.STRING, + number=1, + ) + title: str = proto.Field( + proto.STRING, + number=2, + ) + product_suite: str = proto.Field( + proto.STRING, + number=3, + ) + variants: MutableSequence[str] = proto.RepeatedField( + proto.STRING, + number=4, + ) + lifecycle_state: gcp_lifecycle_state.LifecycleState = proto.Field( + proto.ENUM, + number=5, + enum=gcp_lifecycle_state.LifecycleState, + ) + replaced: bool = proto.Field( + proto.BOOL, + number=6, + ) + replacement: str = proto.Field( + proto.STRING, + number=7, + ) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/types/logical_product_variant.py b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/types/logical_product_variant.py new file mode 100644 index 000000000000..8562f4f80724 --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/types/logical_product_variant.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import MutableMapping, MutableSequence + +import proto # type: ignore + +from google.cloud.productregistry_v1.types import lifecycle_state as gcp_lifecycle_state + +__protobuf__ = proto.module( + package="google.cloud.productregistry.v1", + manifest={ + "LogicalProductVariant", + }, +) + + +class LogicalProductVariant(proto.Message): + r"""Represents a distinct offering derived from a primary product + that retains core functionalities but offers specialized + features for a specific market segment. + + Attributes: + name (str): + Identifier. The resource name of the LogicalProductVariant. + Format: logicalProducts/{logical_product}/variants/{variant} + title (str): + Display name of the LogicalProductVariant. + lifecycle_state (google.cloud.productregistry_v1.types.LifecycleState): + Output only. Current Lifecycle state of the + logical product variant. + replaced (bool): + Output only. Indicates whether the logical product variant + has been replaced. If ``false``, the variant is active. If + ``true``, the variant has been replaced by another type, and + the ``replacement`` field contains the resource name of that + replacement. + replacement (str): + Output only. The resource name of the Logical Entity that + the logical product variant is replaced by. This field is + only populated when this logical product variant is replaced + by some other type. Eg: logicalProducts/{logical_product}, + productSuites/{product_suite}, etc. + """ + + name: str = proto.Field( + proto.STRING, + number=1, + ) + title: str = proto.Field( + proto.STRING, + number=2, + ) + lifecycle_state: gcp_lifecycle_state.LifecycleState = proto.Field( + proto.ENUM, + number=3, + enum=gcp_lifecycle_state.LifecycleState, + ) + replaced: bool = proto.Field( + proto.BOOL, + number=4, + ) + replacement: str = proto.Field( + proto.STRING, + number=5, + ) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/packages/google-cloud-productregistry/google/cloud/productregistry_v1/types/product_suite.py b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/types/product_suite.py new file mode 100644 index 000000000000..02fce48408e8 --- /dev/null +++ b/packages/google-cloud-productregistry/google/cloud/productregistry_v1/types/product_suite.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import MutableMapping, MutableSequence + +import proto # type: ignore + +__protobuf__ = proto.module( + package="google.cloud.productregistry.v1", + manifest={ + "ProductSuite", + }, +) + + +class ProductSuite(proto.Message): + r"""Represents a unified grouping of products sharing a common + brand and market positioning. + + Attributes: + name (str): + Identifier. The resource name of the ProductSuite. Format: + productSuites/{product_suite} + title (str): + Title of the ProductSuite. + logical_products (MutableSequence[str]): + Output only. LogicalProducts under this suite. Format: + logicalProducts/{logical_product} + replaced (bool): + Output only. Indicates whether the product suite has been + replaced. If ``false``, the product suite is active. If + ``true``, the product suite has been replaced by another + type, and the ``replacement`` field contains the resource + name of that replacement. + replacement (str): + Output only. The resource name of the Logical Entity that + the product suite is replaced by. This field is only + populated when this product suite is replaced by some other + type. Eg: logicalProducts/{logical_product}, + logicalProducts/{logical_product}/variants/{variant}, etc. + """ + + name: str = proto.Field( + proto.STRING, + number=1, + ) + title: str = proto.Field( + proto.STRING, + number=2, + ) + logical_products: MutableSequence[str] = proto.RepeatedField( + proto.STRING, + number=3, + ) + replaced: bool = proto.Field( + proto.BOOL, + number=4, + ) + replacement: str = proto.Field( + proto.STRING, + number=5, + ) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/packages/google-cloud-productregistry/noxfile.py b/packages/google-cloud-productregistry/noxfile.py new file mode 100644 index 000000000000..51955ff5533d --- /dev/null +++ b/packages/google-cloud-productregistry/noxfile.py @@ -0,0 +1,648 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 +import pathlib +import re +import shutil +import warnings +from typing import Dict, List + +import nox + +RUFF_VERSION = "ruff==0.14.14" + +LINT_PATHS = ["docs", "google", "tests", "noxfile.py", "setup.py"] + +# Add samples to the list of directories to format if the directory exists. +if os.path.isdir("samples"): + LINT_PATHS.append("samples") + +ALL_PYTHON = [ + "3.10", + "3.11", + "3.12", + "3.13", + "3.14", + "3.15", +] + +DEFAULT_PYTHON_VERSION = "3.14" + +PREVIEW_PYTHON_VERSION = "3.15" + +CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) + +if (CURRENT_DIRECTORY / "testing").exists(): + LOWER_BOUND_CONSTRAINTS_FILE = ( + CURRENT_DIRECTORY / "testing" / f"constraints-{ALL_PYTHON[0]}.txt" + ) +else: + LOWER_BOUND_CONSTRAINTS_FILE = CURRENT_DIRECTORY / "constraints.txt" +PACKAGE_NAME = "google-cloud-productregistry" + +UNIT_TEST_STANDARD_DEPENDENCIES = [ + "mock", + "asyncmock", + "pytest", + "pytest-cov", + "pytest-asyncio", +] +UNIT_TEST_EXTERNAL_DEPENDENCIES: List[str] = [] +UNIT_TEST_LOCAL_DEPENDENCIES: List[str] = [] +UNIT_TEST_DEPENDENCIES: List[str] = [] +UNIT_TEST_EXTRAS: List[str] = [] +UNIT_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = {} + +SYSTEM_TEST_PYTHON_VERSIONS: List[str] = ALL_PYTHON +SYSTEM_TEST_STANDARD_DEPENDENCIES = [ + "mock", + "pytest", + "google-cloud-testutils", +] +SYSTEM_TEST_EXTERNAL_DEPENDENCIES: List[str] = [] +SYSTEM_TEST_LOCAL_DEPENDENCIES: List[str] = [] +SYSTEM_TEST_DEPENDENCIES: List[str] = [] +SYSTEM_TEST_EXTRAS: List[str] = [] +SYSTEM_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = {} + +nox.options.sessions = [ + "unit", + "system", + "cover", + "lint", + "lint_setup_py", + "blacken", + "docs", +] + +# Error if a python version is missing +nox.options.error_on_missing_interpreters = True + + +@nox.session(python=ALL_PYTHON) +def mypy(session): + """Run the type checker.""" + # TODO(https://github.com/googleapis/gapic-generator-python/issues/2579): + # use the latest version of mypy + session.install( + "mypy<1.16.0", + "types-requests", + "types-protobuf", + ) + session.install(".") + session.run( + "mypy", + f"--config-file={MYPY_CONFIG_FILE}", + "-p", + "google", + "--check-untyped-defs", + *session.posargs, + ) + + +@nox.session +def update_lower_bounds(session): + """Update lower bounds in constraints.txt to match setup.py""" + session.install("google-cloud-testutils") + session.install(".") + + session.run( + "lower-bound-checker", + "update", + "--package-name", + PACKAGE_NAME, + "--constraints-file", + str(LOWER_BOUND_CONSTRAINTS_FILE), + ) + + +@nox.session +def check_lower_bounds(session): + """Check lower bounds in setup.py are reflected in constraints file""" + session.install("google-cloud-testutils") + session.install(".") + + session.run( + "lower-bound-checker", + "check", + "--package-name", + PACKAGE_NAME, + "--constraints-file", + str(LOWER_BOUND_CONSTRAINTS_FILE), + ) + + +@nox.session(python=DEFAULT_PYTHON_VERSION) +def lint(session): + """Run linters. + + Returns a failure if the linters find linting errors or sufficiently + serious code quality issues. + """ + session.install("flake8", RUFF_VERSION) + + # 2. Check formatting + session.run( + "ruff", + "format", + "--check", + f"--target-version=py{ALL_PYTHON[0].replace('.', '')}", + "--line-length=88", + *LINT_PATHS, + ) + + session.run("flake8", "google", "tests") + + +@nox.session(python=DEFAULT_PYTHON_VERSION) +def blacken(session): + """(Deprecated) Legacy session. Please use 'nox -s format'.""" + session.log( + "WARNING: The 'blacken' session is deprecated and will be removed in a future release. Please use 'nox -s format' in the future." + ) + + # Just run the ruff formatter (keeping legacy behavior of only formatting, not sorting imports) + session.install(RUFF_VERSION) + session.run( + "ruff", + "format", + f"--target-version=py{ALL_PYTHON[0].replace('.', '')}", + "--line-length=88", + *LINT_PATHS, + ) + + +@nox.session(python=DEFAULT_PYTHON_VERSION) +def format(session): + """ + Run ruff to sort imports and format code. + """ + # 1. Install ruff (skipped automatically if you run with --no-venv) + session.install(RUFF_VERSION) + + # 2. Run Ruff to fix imports + # check --select I: Enables strict import sorting + # --fix: Applies the changes automatically + session.run( + "ruff", + "check", + "--select", + "I", + "--fix", + f"--target-version=py{ALL_PYTHON[0].replace('.', '')}", + "--line-length=88", # Standard Black line length + *LINT_PATHS, + ) + + # 3. Run Ruff to format code + session.run( + "ruff", + "format", + f"--target-version=py{ALL_PYTHON[0].replace('.', '')}", + "--line-length=88", # Standard Black line length + *LINT_PATHS, + ) + + +@nox.session(python=DEFAULT_PYTHON_VERSION) +def lint_setup_py(session): + """Verify that setup.py is valid (including RST check).""" + session.install("setuptools", "docutils", "pygments") + session.run("python", "setup.py", "check", "--restructuredtext", "--strict") + + +def install_unittest_dependencies(session, *constraints): + standard_deps = UNIT_TEST_STANDARD_DEPENDENCIES + UNIT_TEST_DEPENDENCIES + session.install(*standard_deps, *constraints) + + if UNIT_TEST_EXTERNAL_DEPENDENCIES: + warnings.warn( + "'unit_test_external_dependencies' is deprecated. Instead, please " + "use 'unit_test_dependencies' or 'unit_test_local_dependencies'.", + DeprecationWarning, + ) + session.install(*UNIT_TEST_EXTERNAL_DEPENDENCIES, *constraints) + + if UNIT_TEST_LOCAL_DEPENDENCIES: + session.install(*UNIT_TEST_LOCAL_DEPENDENCIES, *constraints) + + if UNIT_TEST_EXTRAS_BY_PYTHON: + extras = UNIT_TEST_EXTRAS_BY_PYTHON.get(session.python, []) + elif UNIT_TEST_EXTRAS: + extras = UNIT_TEST_EXTRAS + else: + extras = [] + + if extras: + session.install("-e", f".[{','.join(extras)}]", *constraints) + else: + session.install("-e", ".", *constraints) + + +@nox.session(python=ALL_PYTHON) +@nox.parametrize( + "protobuf_implementation", + ["python", "upb"], +) +def unit(session, protobuf_implementation): + # Install all test dependencies, then install this package in-place. + + constraints_path = str( + CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" + ) + install_unittest_dependencies(session, "-c", constraints_path) + + # Run py.test against the unit tests. + session.run( + "py.test", + "--quiet", + f"--junitxml=unit_{session.python}_sponge_log.xml", + "--cov=google", + "--cov=tests/unit", + "--cov-append", + "--cov-config=.coveragerc", + "--cov-report=", + "--cov-fail-under=0", + os.path.join("tests", "unit"), + *session.posargs, + env={ + "PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION": protobuf_implementation, + }, + ) + + +def install_systemtest_dependencies(session, *constraints): + if session.python >= "3.12": + session.install("--pre", "grpcio>=1.75.1") + else: + session.install("--pre", "grpcio<=1.62.2") + + session.install(*SYSTEM_TEST_STANDARD_DEPENDENCIES, *constraints) + + if SYSTEM_TEST_EXTERNAL_DEPENDENCIES: + session.install(*SYSTEM_TEST_EXTERNAL_DEPENDENCIES, *constraints) + + if SYSTEM_TEST_LOCAL_DEPENDENCIES: + session.install("-e", *SYSTEM_TEST_LOCAL_DEPENDENCIES, *constraints) + + if SYSTEM_TEST_DEPENDENCIES: + session.install("-e", *SYSTEM_TEST_DEPENDENCIES, *constraints) + + if SYSTEM_TEST_EXTRAS_BY_PYTHON: + extras = SYSTEM_TEST_EXTRAS_BY_PYTHON.get(session.python, []) + elif SYSTEM_TEST_EXTRAS: + extras = SYSTEM_TEST_EXTRAS + else: + extras = [] + + if extras: + session.install("-e", f".[{','.join(extras)}]", *constraints) + else: + session.install("-e", ".", *constraints) + + +@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS) +def system(session): + """Run the system test suite.""" + constraints_path = str( + CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" + ) + system_test_path = os.path.join("tests", "system.py") + system_test_folder_path = os.path.join("tests", "system") + + # Check the value of `RUN_SYSTEM_TESTS` env var. It defaults to true. + if os.environ.get("RUN_SYSTEM_TESTS", "true") == "false": + session.skip("RUN_SYSTEM_TESTS is set to false, skipping") + # Install pyopenssl for mTLS testing. + if os.environ.get("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true": + session.install("pyopenssl") + + system_test_exists = os.path.exists(system_test_path) + system_test_folder_exists = os.path.exists(system_test_folder_path) + # Sanity check: only run tests if found. + if not system_test_exists and not system_test_folder_exists: + session.skip("System tests were not found") + + install_systemtest_dependencies(session, "-c", constraints_path) + + # Run py.test against the system tests. + if system_test_exists: + session.run( + "py.test", + "--quiet", + f"--junitxml=system_{session.python}_sponge_log.xml", + system_test_path, + *session.posargs, + ) + if system_test_folder_exists: + session.run( + "py.test", + "--quiet", + f"--junitxml=system_{session.python}_sponge_log.xml", + system_test_folder_path, + *session.posargs, + ) + + +@nox.session(python=DEFAULT_PYTHON_VERSION) +def cover(session): + """Run the final coverage report. + + This outputs the coverage report aggregating coverage from the unit + test runs (not system test runs), and then erases coverage data. + """ + session.install("coverage", "pytest-cov") + session.run("coverage", "report", "--show-missing", "--fail-under=100") + + session.run("coverage", "erase") + + +@nox.session(python="3.10") +def docs(session): + """Build the docs for this library.""" + + session.install("-e", ".") + session.install( + # We need to pin to specific versions of the `sphinxcontrib-*` packages + # which still support sphinx 4.x. + # See https://github.com/googleapis/sphinx-docfx-yaml/issues/344 + # and https://github.com/googleapis/sphinx-docfx-yaml/issues/345. + "sphinxcontrib-applehelp==1.0.4", + "sphinxcontrib-devhelp==1.0.2", + "sphinxcontrib-htmlhelp==2.0.1", + "sphinxcontrib-qthelp==1.0.3", + "sphinxcontrib-serializinghtml==1.1.5", + "sphinx==4.5.0", + "alabaster", + "recommonmark", + ) + + shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) + session.run( + "sphinx-build", + "-T", # show full traceback on exception + "-N", # no colors + "-b", + "html", # builder + "-d", + os.path.join("docs", "_build", "doctrees", ""), # cache directory + # paths to build: + os.path.join("docs", ""), + os.path.join("docs", "_build", "html", ""), + ) + + +@nox.session(python="3.10") +def docfx(session): + """Build the docfx yaml files for this library.""" + + session.install("-e", ".") + session.install( + # We need to pin to specific versions of the `sphinxcontrib-*` packages + # which still support sphinx 4.x. + # See https://github.com/googleapis/sphinx-docfx-yaml/issues/344 + # and https://github.com/googleapis/sphinx-docfx-yaml/issues/345. + "sphinxcontrib-applehelp==1.0.4", + "sphinxcontrib-devhelp==1.0.2", + "sphinxcontrib-htmlhelp==2.0.1", + "sphinxcontrib-qthelp==1.0.3", + "sphinxcontrib-serializinghtml==1.1.5", + "gcp-sphinx-docfx-yaml", + "alabaster", + "recommonmark", + ) + + shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) + session.run( + "sphinx-build", + "-T", # show full traceback on exception + "-N", # no colors + "-D", + ( + "extensions=sphinx.ext.autodoc," + "sphinx.ext.autosummary," + "docfx_yaml.extension," + "sphinx.ext.intersphinx," + "sphinx.ext.coverage," + "sphinx.ext.napoleon," + "sphinx.ext.todo," + "sphinx.ext.viewcode," + "recommonmark" + ), + "-b", + "html", + "-d", + os.path.join("docs", "_build", "doctrees", ""), + os.path.join("docs", ""), + os.path.join("docs", "_build", "html", ""), + ) + + +@nox.session(python=PREVIEW_PYTHON_VERSION) +@nox.parametrize( + "protobuf_implementation", + ["python", "upb"], +) +def prerelease_deps(session, protobuf_implementation): + """ + Run all tests with pre-release versions of dependencies installed + rather than the standard non pre-release versions. + Pre-release versions can be installed using + `pip install --pre `. + """ + + # Install all dependencies + session.install("-e", ".") + + # Install dependencies for the unit test environment + unit_deps_all = UNIT_TEST_STANDARD_DEPENDENCIES + UNIT_TEST_EXTERNAL_DEPENDENCIES + session.install(*unit_deps_all) + + # Because we test minimum dependency versions on the minimum Python + # version, the first version we test with in the unit tests sessions has a + # constraints file containing all dependencies and extras. + with open( + CURRENT_DIRECTORY / "testing" / f"constraints-{ALL_PYTHON[0]}.txt", + encoding="utf-8", + ) as constraints_file: + constraints_text = constraints_file.read() + + # Ignore leading whitespace and comment lines. + constraints_deps = [ + match.group(1) + for match in re.finditer( + r"^\s*(\S+)(?===\S+)", constraints_text, flags=re.MULTILINE + ) + ] + + # Install dependencies specified in `testing/constraints-X.txt`. + session.install(*constraints_deps) + + # Note: If a dependency is added to the `prerel_deps` list, + # the `core_dependencies_from_source` list in the `core_deps_from_source` + # nox session should also be updated. + prerel_deps = [ + "googleapis-common-protos", + "google-api-core", + "google-auth", + "grpc-google-iam-v1", + "grpcio>=1.75.1" if session.python >= "3.12" else "grpcio<=1.62.2", + "grpcio-status", + "protobuf", + "proto-plus", + ] + + deps_dir = CURRENT_DIRECTORY.parent + while deps_dir.name != "packages" and deps_dir.parent != deps_dir: + deps_dir = deps_dir.parent + + # Extract the base package name, safely ignoring version bounds and spaces + # (e.g., "grpcio>=1.75.1" becomes "grpcio") + parsed_deps = { + dep: re.match(r"^([a-zA-Z0-9_-]+)", dep).group(1) for dep in prerel_deps + } + + # Dynamically sort local packages vs PyPI dependencies + local_paths = [] + pypi_deps = [] + + for dep, pkg_name in parsed_deps.items(): + if (deps_dir / pkg_name).exists(): + local_paths.append(str(deps_dir / pkg_name)) + else: + pypi_deps.append(dep) + + # Batch pip installations to avoid sequential overhead + if local_paths: + session.install(*local_paths, "--no-deps", "--ignore-installed") + if pypi_deps: + session.install(*pypi_deps, "--pre", "--no-deps", "--ignore-installed") + + # TODO(https://github.com/grpc/grpc/issues/38965): Add `grpcio-status`` + # to the dictionary below once this bug is fixed. + # TODO(https://github.com/googleapis/google-cloud-python/issues/13643): Add + # `googleapis-common-protos` and `grpc-google-iam-v1` to the dictionary below + # once this bug is fixed. + package_namespaces = { + "google-api-core": "google.api_core", + "google-auth": "google.auth", + "grpcio": "grpc", + "protobuf": "google.protobuf", + "proto-plus": "proto", + } + + # Reuse the parsed names for logging and version verification + for dep, pkg_name in parsed_deps.items(): + print(f"Installed {dep}") + version_namespace = package_namespaces.get(pkg_name) + + if version_namespace: + session.run( + "python", + "-c", + f"import {version_namespace}; print({version_namespace}.__version__)", + ) + + session.run( + "py.test", + "tests/unit", + env={ + "PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION": protobuf_implementation, + }, + ) + + +@nox.session(python=PREVIEW_PYTHON_VERSION) +@nox.parametrize( + "protobuf_implementation", + ["python", "upb"], +) +def core_deps_from_source(session, protobuf_implementation): + """Run all tests with core dependencies installed from source + rather than pulling the dependencies from PyPI. + """ + + # Install all dependencies + session.install("-e", ".") + + # Install dependencies for the unit test environment + unit_deps_all = UNIT_TEST_STANDARD_DEPENDENCIES + UNIT_TEST_EXTERNAL_DEPENDENCIES + session.install(*unit_deps_all) + + # Because we test minimum dependency versions on the minimum Python + # version, the first version we test with in the unit tests sessions has a + # constraints file containing all dependencies and extras. + with open( + CURRENT_DIRECTORY / "testing" / f"constraints-{ALL_PYTHON[0]}.txt", + encoding="utf-8", + ) as constraints_file: + constraints_text = constraints_file.read() + + # Ignore leading whitespace and comment lines. + constraints_deps = [ + match.group(1) + for match in re.finditer( + r"^\s*(\S+)(?===\S+)", constraints_text, flags=re.MULTILINE + ) + ] + + # Install dependencies specified in `testing/constraints-X.txt`. + session.install(*constraints_deps) + + # TODO(https://github.com/googleapis/gapic-generator-python/issues/2358): `grpcio` and + # `grpcio-status` should be added to the list below so that they are installed from source, + # rather than PyPI. + # TODO(https://github.com/googleapis/gapic-generator-python/issues/2357): `protobuf` should be + # added to the list below so that it is installed from source, rather than PyPI + # Note: If a dependency is added to the `core_dependencies_from_source` list, + # the `prerel_deps` list in the `prerelease_deps` nox session should also be updated. + core_dependencies_from_source = [ + "googleapis-common-protos", + "google-api-core", + "google-auth", + "grpc-google-iam-v1", + "proto-plus", + ] + + deps_dir = CURRENT_DIRECTORY.parent + while deps_dir.name != "packages" and deps_dir.parent != deps_dir: + deps_dir = deps_dir.parent + + # Batch the pip installation to avoid sequential overhead + dep_paths = [str(deps_dir / dep) for dep in core_dependencies_from_source] + + session.install(*dep_paths, "--no-deps", "--ignore-installed") + print( + f"Installed {', '.join(core_dependencies_from_source)} locally from {deps_dir}" + ) + + session.run( + "py.test", + "tests/unit", + env={ + "PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION": protobuf_implementation, + }, + ) diff --git a/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_async.py b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_async.py new file mode 100644 index 000000000000..bec046d99609 --- /dev/null +++ b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_async.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 GetLogicalProduct +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-productregistry + + +# [START cloudproductregistry_v1_generated_CloudProductRegistryReadService_GetLogicalProduct_async] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import productregistry_v1 + + +async def sample_get_logical_product(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceAsyncClient() + + # Initialize request argument(s) + request = productregistry_v1.GetLogicalProductRequest( + name="name_value", + ) + + # Make the request + response = await client.get_logical_product(request=request) + + # Handle the response + print(response) + + +# [END cloudproductregistry_v1_generated_CloudProductRegistryReadService_GetLogicalProduct_async] diff --git a/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_sync.py b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_sync.py new file mode 100644 index 000000000000..d4d1a68977bd --- /dev/null +++ b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_sync.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 GetLogicalProduct +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-productregistry + + +# [START cloudproductregistry_v1_generated_CloudProductRegistryReadService_GetLogicalProduct_sync] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import productregistry_v1 + + +def sample_get_logical_product(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceClient() + + # Initialize request argument(s) + request = productregistry_v1.GetLogicalProductRequest( + name="name_value", + ) + + # Make the request + response = client.get_logical_product(request=request) + + # Handle the response + print(response) + + +# [END cloudproductregistry_v1_generated_CloudProductRegistryReadService_GetLogicalProduct_sync] diff --git a/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_variant_async.py b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_variant_async.py new file mode 100644 index 000000000000..9768ca7c5e61 --- /dev/null +++ b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_variant_async.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 GetLogicalProductVariant +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-productregistry + + +# [START cloudproductregistry_v1_generated_CloudProductRegistryReadService_GetLogicalProductVariant_async] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import productregistry_v1 + + +async def sample_get_logical_product_variant(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceAsyncClient() + + # Initialize request argument(s) + request = productregistry_v1.GetLogicalProductVariantRequest( + name="name_value", + ) + + # Make the request + response = await client.get_logical_product_variant(request=request) + + # Handle the response + print(response) + + +# [END cloudproductregistry_v1_generated_CloudProductRegistryReadService_GetLogicalProductVariant_async] diff --git a/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_variant_sync.py b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_variant_sync.py new file mode 100644 index 000000000000..9a2ff89417f7 --- /dev/null +++ b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_variant_sync.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 GetLogicalProductVariant +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-productregistry + + +# [START cloudproductregistry_v1_generated_CloudProductRegistryReadService_GetLogicalProductVariant_sync] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import productregistry_v1 + + +def sample_get_logical_product_variant(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceClient() + + # Initialize request argument(s) + request = productregistry_v1.GetLogicalProductVariantRequest( + name="name_value", + ) + + # Make the request + response = client.get_logical_product_variant(request=request) + + # Handle the response + print(response) + + +# [END cloudproductregistry_v1_generated_CloudProductRegistryReadService_GetLogicalProductVariant_sync] diff --git a/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_product_suite_async.py b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_product_suite_async.py new file mode 100644 index 000000000000..bc7f461d2edd --- /dev/null +++ b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_product_suite_async.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 GetProductSuite +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-productregistry + + +# [START cloudproductregistry_v1_generated_CloudProductRegistryReadService_GetProductSuite_async] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import productregistry_v1 + + +async def sample_get_product_suite(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceAsyncClient() + + # Initialize request argument(s) + request = productregistry_v1.GetProductSuiteRequest( + name="name_value", + ) + + # Make the request + response = await client.get_product_suite(request=request) + + # Handle the response + print(response) + + +# [END cloudproductregistry_v1_generated_CloudProductRegistryReadService_GetProductSuite_async] diff --git a/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_product_suite_sync.py b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_product_suite_sync.py new file mode 100644 index 000000000000..315bb935fa28 --- /dev/null +++ b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_product_suite_sync.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 GetProductSuite +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-productregistry + + +# [START cloudproductregistry_v1_generated_CloudProductRegistryReadService_GetProductSuite_sync] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import productregistry_v1 + + +def sample_get_product_suite(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceClient() + + # Initialize request argument(s) + request = productregistry_v1.GetProductSuiteRequest( + name="name_value", + ) + + # Make the request + response = client.get_product_suite(request=request) + + # Handle the response + print(response) + + +# [END cloudproductregistry_v1_generated_CloudProductRegistryReadService_GetProductSuite_sync] diff --git a/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_product_variants_async.py b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_product_variants_async.py new file mode 100644 index 000000000000..44ee99dc790f --- /dev/null +++ b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_product_variants_async.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 ListLogicalProductVariants +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-productregistry + + +# [START cloudproductregistry_v1_generated_CloudProductRegistryReadService_ListLogicalProductVariants_async] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import productregistry_v1 + + +async def sample_list_logical_product_variants(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceAsyncClient() + + # Initialize request argument(s) + request = productregistry_v1.ListLogicalProductVariantsRequest( + parent="parent_value", + ) + + # Make the request + page_result = client.list_logical_product_variants(request=request) + + # Handle the response + async for response in page_result: + print(response) + + +# [END cloudproductregistry_v1_generated_CloudProductRegistryReadService_ListLogicalProductVariants_async] diff --git a/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_product_variants_sync.py b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_product_variants_sync.py new file mode 100644 index 000000000000..cfc1a7a8a3d5 --- /dev/null +++ b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_product_variants_sync.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 ListLogicalProductVariants +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-productregistry + + +# [START cloudproductregistry_v1_generated_CloudProductRegistryReadService_ListLogicalProductVariants_sync] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import productregistry_v1 + + +def sample_list_logical_product_variants(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceClient() + + # Initialize request argument(s) + request = productregistry_v1.ListLogicalProductVariantsRequest( + parent="parent_value", + ) + + # Make the request + page_result = client.list_logical_product_variants(request=request) + + # Handle the response + for response in page_result: + print(response) + + +# [END cloudproductregistry_v1_generated_CloudProductRegistryReadService_ListLogicalProductVariants_sync] diff --git a/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_products_async.py b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_products_async.py new file mode 100644 index 000000000000..bf238b786868 --- /dev/null +++ b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_products_async.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 ListLogicalProducts +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-productregistry + + +# [START cloudproductregistry_v1_generated_CloudProductRegistryReadService_ListLogicalProducts_async] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import productregistry_v1 + + +async def sample_list_logical_products(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceAsyncClient() + + # Initialize request argument(s) + request = productregistry_v1.ListLogicalProductsRequest() + + # Make the request + page_result = client.list_logical_products(request=request) + + # Handle the response + async for response in page_result: + print(response) + + +# [END cloudproductregistry_v1_generated_CloudProductRegistryReadService_ListLogicalProducts_async] diff --git a/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_products_sync.py b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_products_sync.py new file mode 100644 index 000000000000..e2afac5a80b9 --- /dev/null +++ b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_products_sync.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 ListLogicalProducts +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-productregistry + + +# [START cloudproductregistry_v1_generated_CloudProductRegistryReadService_ListLogicalProducts_sync] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import productregistry_v1 + + +def sample_list_logical_products(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceClient() + + # Initialize request argument(s) + request = productregistry_v1.ListLogicalProductsRequest() + + # Make the request + page_result = client.list_logical_products(request=request) + + # Handle the response + for response in page_result: + print(response) + + +# [END cloudproductregistry_v1_generated_CloudProductRegistryReadService_ListLogicalProducts_sync] diff --git a/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_product_suites_async.py b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_product_suites_async.py new file mode 100644 index 000000000000..323cafe3095e --- /dev/null +++ b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_product_suites_async.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 ListProductSuites +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-productregistry + + +# [START cloudproductregistry_v1_generated_CloudProductRegistryReadService_ListProductSuites_async] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import productregistry_v1 + + +async def sample_list_product_suites(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceAsyncClient() + + # Initialize request argument(s) + request = productregistry_v1.ListProductSuitesRequest() + + # Make the request + page_result = client.list_product_suites(request=request) + + # Handle the response + async for response in page_result: + print(response) + + +# [END cloudproductregistry_v1_generated_CloudProductRegistryReadService_ListProductSuites_async] diff --git a/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_product_suites_sync.py b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_product_suites_sync.py new file mode 100644 index 000000000000..832a1728006d --- /dev/null +++ b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_product_suites_sync.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 ListProductSuites +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-productregistry + + +# [START cloudproductregistry_v1_generated_CloudProductRegistryReadService_ListProductSuites_sync] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import productregistry_v1 + + +def sample_list_product_suites(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceClient() + + # Initialize request argument(s) + request = productregistry_v1.ListProductSuitesRequest() + + # Make the request + page_result = client.list_product_suites(request=request) + + # Handle the response + for response in page_result: + print(response) + + +# [END cloudproductregistry_v1_generated_CloudProductRegistryReadService_ListProductSuites_sync] diff --git a/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_lookup_entity_async.py b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_lookup_entity_async.py new file mode 100644 index 000000000000..96398bb222c9 --- /dev/null +++ b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_lookup_entity_async.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 LookupEntity +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-productregistry + + +# [START cloudproductregistry_v1_generated_CloudProductRegistryReadService_LookupEntity_async] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import productregistry_v1 + + +async def sample_lookup_entity(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceAsyncClient() + + # Initialize request argument(s) + request = productregistry_v1.LookupEntityRequest( + lookup_uri="lookup_uri_value", + ) + + # Make the request + response = await client.lookup_entity(request=request) + + # Handle the response + print(response) + + +# [END cloudproductregistry_v1_generated_CloudProductRegistryReadService_LookupEntity_async] diff --git a/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_lookup_entity_sync.py b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_lookup_entity_sync.py new file mode 100644 index 000000000000..1b4705d56a2a --- /dev/null +++ b/packages/google-cloud-productregistry/samples/generated_samples/cloudproductregistry_v1_generated_cloud_product_registry_read_service_lookup_entity_sync.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 LookupEntity +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-productregistry + + +# [START cloudproductregistry_v1_generated_CloudProductRegistryReadService_LookupEntity_sync] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import productregistry_v1 + + +def sample_lookup_entity(): + # Create a client + client = productregistry_v1.CloudProductRegistryReadServiceClient() + + # Initialize request argument(s) + request = productregistry_v1.LookupEntityRequest( + lookup_uri="lookup_uri_value", + ) + + # Make the request + response = client.lookup_entity(request=request) + + # Handle the response + print(response) + + +# [END cloudproductregistry_v1_generated_CloudProductRegistryReadService_LookupEntity_sync] diff --git a/packages/google-cloud-productregistry/samples/generated_samples/snippet_metadata_google.cloud.productregistry.v1.json b/packages/google-cloud-productregistry/samples/generated_samples/snippet_metadata_google.cloud.productregistry.v1.json new file mode 100644 index 000000000000..73399b526d0d --- /dev/null +++ b/packages/google-cloud-productregistry/samples/generated_samples/snippet_metadata_google.cloud.productregistry.v1.json @@ -0,0 +1,1118 @@ +{ + "clientLibrary": { + "apis": [ + { + "id": "google.cloud.productregistry.v1", + "version": "v1" + } + ], + "language": "PYTHON", + "name": "google-cloud-productregistry", + "version": "0.1.0" + }, + "snippets": [ + { + "canonical": true, + "clientMethod": { + "async": true, + "client": { + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceAsyncClient", + "shortName": "CloudProductRegistryReadServiceAsyncClient" + }, + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceAsyncClient.get_logical_product_variant", + "method": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService.GetLogicalProductVariant", + "service": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "shortName": "CloudProductRegistryReadService" + }, + "shortName": "GetLogicalProductVariant" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.productregistry_v1.types.GetLogicalProductVariantRequest" + }, + { + "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.cloud.productregistry_v1.types.LogicalProductVariant", + "shortName": "get_logical_product_variant" + }, + "description": "Sample for GetLogicalProductVariant", + "file": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_variant_async.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "cloudproductregistry_v1_generated_CloudProductRegistryReadService_GetLogicalProductVariant_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": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_variant_async.py" + }, + { + "canonical": true, + "clientMethod": { + "client": { + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient", + "shortName": "CloudProductRegistryReadServiceClient" + }, + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.get_logical_product_variant", + "method": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService.GetLogicalProductVariant", + "service": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "shortName": "CloudProductRegistryReadService" + }, + "shortName": "GetLogicalProductVariant" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.productregistry_v1.types.GetLogicalProductVariantRequest" + }, + { + "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.cloud.productregistry_v1.types.LogicalProductVariant", + "shortName": "get_logical_product_variant" + }, + "description": "Sample for GetLogicalProductVariant", + "file": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_variant_sync.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "cloudproductregistry_v1_generated_CloudProductRegistryReadService_GetLogicalProductVariant_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": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_variant_sync.py" + }, + { + "canonical": true, + "clientMethod": { + "async": true, + "client": { + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceAsyncClient", + "shortName": "CloudProductRegistryReadServiceAsyncClient" + }, + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceAsyncClient.get_logical_product", + "method": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService.GetLogicalProduct", + "service": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "shortName": "CloudProductRegistryReadService" + }, + "shortName": "GetLogicalProduct" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.productregistry_v1.types.GetLogicalProductRequest" + }, + { + "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.cloud.productregistry_v1.types.LogicalProduct", + "shortName": "get_logical_product" + }, + "description": "Sample for GetLogicalProduct", + "file": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_async.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "cloudproductregistry_v1_generated_CloudProductRegistryReadService_GetLogicalProduct_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": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_async.py" + }, + { + "canonical": true, + "clientMethod": { + "client": { + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient", + "shortName": "CloudProductRegistryReadServiceClient" + }, + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.get_logical_product", + "method": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService.GetLogicalProduct", + "service": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "shortName": "CloudProductRegistryReadService" + }, + "shortName": "GetLogicalProduct" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.productregistry_v1.types.GetLogicalProductRequest" + }, + { + "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.cloud.productregistry_v1.types.LogicalProduct", + "shortName": "get_logical_product" + }, + "description": "Sample for GetLogicalProduct", + "file": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_sync.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "cloudproductregistry_v1_generated_CloudProductRegistryReadService_GetLogicalProduct_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": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_logical_product_sync.py" + }, + { + "canonical": true, + "clientMethod": { + "async": true, + "client": { + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceAsyncClient", + "shortName": "CloudProductRegistryReadServiceAsyncClient" + }, + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceAsyncClient.get_product_suite", + "method": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService.GetProductSuite", + "service": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "shortName": "CloudProductRegistryReadService" + }, + "shortName": "GetProductSuite" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.productregistry_v1.types.GetProductSuiteRequest" + }, + { + "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.cloud.productregistry_v1.types.ProductSuite", + "shortName": "get_product_suite" + }, + "description": "Sample for GetProductSuite", + "file": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_product_suite_async.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "cloudproductregistry_v1_generated_CloudProductRegistryReadService_GetProductSuite_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": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_product_suite_async.py" + }, + { + "canonical": true, + "clientMethod": { + "client": { + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient", + "shortName": "CloudProductRegistryReadServiceClient" + }, + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.get_product_suite", + "method": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService.GetProductSuite", + "service": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "shortName": "CloudProductRegistryReadService" + }, + "shortName": "GetProductSuite" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.productregistry_v1.types.GetProductSuiteRequest" + }, + { + "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.cloud.productregistry_v1.types.ProductSuite", + "shortName": "get_product_suite" + }, + "description": "Sample for GetProductSuite", + "file": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_product_suite_sync.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "cloudproductregistry_v1_generated_CloudProductRegistryReadService_GetProductSuite_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": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_get_product_suite_sync.py" + }, + { + "canonical": true, + "clientMethod": { + "async": true, + "client": { + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceAsyncClient", + "shortName": "CloudProductRegistryReadServiceAsyncClient" + }, + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceAsyncClient.list_logical_product_variants", + "method": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService.ListLogicalProductVariants", + "service": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "shortName": "CloudProductRegistryReadService" + }, + "shortName": "ListLogicalProductVariants" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.productregistry_v1.types.ListLogicalProductVariantsRequest" + }, + { + "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.cloud.productregistry_v1.services.cloud_product_registry_read_service.pagers.ListLogicalProductVariantsAsyncPager", + "shortName": "list_logical_product_variants" + }, + "description": "Sample for ListLogicalProductVariants", + "file": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_product_variants_async.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "cloudproductregistry_v1_generated_CloudProductRegistryReadService_ListLogicalProductVariants_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": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_product_variants_async.py" + }, + { + "canonical": true, + "clientMethod": { + "client": { + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient", + "shortName": "CloudProductRegistryReadServiceClient" + }, + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.list_logical_product_variants", + "method": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService.ListLogicalProductVariants", + "service": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "shortName": "CloudProductRegistryReadService" + }, + "shortName": "ListLogicalProductVariants" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.productregistry_v1.types.ListLogicalProductVariantsRequest" + }, + { + "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.cloud.productregistry_v1.services.cloud_product_registry_read_service.pagers.ListLogicalProductVariantsPager", + "shortName": "list_logical_product_variants" + }, + "description": "Sample for ListLogicalProductVariants", + "file": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_product_variants_sync.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "cloudproductregistry_v1_generated_CloudProductRegistryReadService_ListLogicalProductVariants_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": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_product_variants_sync.py" + }, + { + "canonical": true, + "clientMethod": { + "async": true, + "client": { + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceAsyncClient", + "shortName": "CloudProductRegistryReadServiceAsyncClient" + }, + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceAsyncClient.list_logical_products", + "method": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService.ListLogicalProducts", + "service": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "shortName": "CloudProductRegistryReadService" + }, + "shortName": "ListLogicalProducts" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.productregistry_v1.types.ListLogicalProductsRequest" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "resultType": "google.cloud.productregistry_v1.services.cloud_product_registry_read_service.pagers.ListLogicalProductsAsyncPager", + "shortName": "list_logical_products" + }, + "description": "Sample for ListLogicalProducts", + "file": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_products_async.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "cloudproductregistry_v1_generated_CloudProductRegistryReadService_ListLogicalProducts_async", + "segments": [ + { + "end": 51, + "start": 27, + "type": "FULL" + }, + { + "end": 51, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 44, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 47, + "start": 45, + "type": "REQUEST_EXECUTION" + }, + { + "end": 52, + "start": 48, + "type": "RESPONSE_HANDLING" + } + ], + "title": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_products_async.py" + }, + { + "canonical": true, + "clientMethod": { + "client": { + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient", + "shortName": "CloudProductRegistryReadServiceClient" + }, + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.list_logical_products", + "method": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService.ListLogicalProducts", + "service": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "shortName": "CloudProductRegistryReadService" + }, + "shortName": "ListLogicalProducts" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.productregistry_v1.types.ListLogicalProductsRequest" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "resultType": "google.cloud.productregistry_v1.services.cloud_product_registry_read_service.pagers.ListLogicalProductsPager", + "shortName": "list_logical_products" + }, + "description": "Sample for ListLogicalProducts", + "file": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_products_sync.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "cloudproductregistry_v1_generated_CloudProductRegistryReadService_ListLogicalProducts_sync", + "segments": [ + { + "end": 51, + "start": 27, + "type": "FULL" + }, + { + "end": 51, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 44, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 47, + "start": 45, + "type": "REQUEST_EXECUTION" + }, + { + "end": 52, + "start": 48, + "type": "RESPONSE_HANDLING" + } + ], + "title": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_logical_products_sync.py" + }, + { + "canonical": true, + "clientMethod": { + "async": true, + "client": { + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceAsyncClient", + "shortName": "CloudProductRegistryReadServiceAsyncClient" + }, + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceAsyncClient.list_product_suites", + "method": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService.ListProductSuites", + "service": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "shortName": "CloudProductRegistryReadService" + }, + "shortName": "ListProductSuites" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.productregistry_v1.types.ListProductSuitesRequest" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "resultType": "google.cloud.productregistry_v1.services.cloud_product_registry_read_service.pagers.ListProductSuitesAsyncPager", + "shortName": "list_product_suites" + }, + "description": "Sample for ListProductSuites", + "file": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_product_suites_async.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "cloudproductregistry_v1_generated_CloudProductRegistryReadService_ListProductSuites_async", + "segments": [ + { + "end": 51, + "start": 27, + "type": "FULL" + }, + { + "end": 51, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 44, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 47, + "start": 45, + "type": "REQUEST_EXECUTION" + }, + { + "end": 52, + "start": 48, + "type": "RESPONSE_HANDLING" + } + ], + "title": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_product_suites_async.py" + }, + { + "canonical": true, + "clientMethod": { + "client": { + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient", + "shortName": "CloudProductRegistryReadServiceClient" + }, + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.list_product_suites", + "method": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService.ListProductSuites", + "service": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "shortName": "CloudProductRegistryReadService" + }, + "shortName": "ListProductSuites" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.productregistry_v1.types.ListProductSuitesRequest" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "resultType": "google.cloud.productregistry_v1.services.cloud_product_registry_read_service.pagers.ListProductSuitesPager", + "shortName": "list_product_suites" + }, + "description": "Sample for ListProductSuites", + "file": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_product_suites_sync.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "cloudproductregistry_v1_generated_CloudProductRegistryReadService_ListProductSuites_sync", + "segments": [ + { + "end": 51, + "start": 27, + "type": "FULL" + }, + { + "end": 51, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 44, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 47, + "start": 45, + "type": "REQUEST_EXECUTION" + }, + { + "end": 52, + "start": 48, + "type": "RESPONSE_HANDLING" + } + ], + "title": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_list_product_suites_sync.py" + }, + { + "canonical": true, + "clientMethod": { + "async": true, + "client": { + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceAsyncClient", + "shortName": "CloudProductRegistryReadServiceAsyncClient" + }, + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceAsyncClient.lookup_entity", + "method": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService.LookupEntity", + "service": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "shortName": "CloudProductRegistryReadService" + }, + "shortName": "LookupEntity" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.productregistry_v1.types.LookupEntityRequest" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "resultType": "google.cloud.productregistry_v1.types.LookupEntityResponse", + "shortName": "lookup_entity" + }, + "description": "Sample for LookupEntity", + "file": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_lookup_entity_async.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "cloudproductregistry_v1_generated_CloudProductRegistryReadService_LookupEntity_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": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_lookup_entity_async.py" + }, + { + "canonical": true, + "clientMethod": { + "client": { + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient", + "shortName": "CloudProductRegistryReadServiceClient" + }, + "fullName": "google.cloud.productregistry_v1.CloudProductRegistryReadServiceClient.lookup_entity", + "method": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService.LookupEntity", + "service": { + "fullName": "google.cloud.productregistry.v1.CloudProductRegistryReadService", + "shortName": "CloudProductRegistryReadService" + }, + "shortName": "LookupEntity" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.productregistry_v1.types.LookupEntityRequest" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "resultType": "google.cloud.productregistry_v1.types.LookupEntityResponse", + "shortName": "lookup_entity" + }, + "description": "Sample for LookupEntity", + "file": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_lookup_entity_sync.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "cloudproductregistry_v1_generated_CloudProductRegistryReadService_LookupEntity_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": "cloudproductregistry_v1_generated_cloud_product_registry_read_service_lookup_entity_sync.py" + } + ] +} diff --git a/packages/google-cloud-productregistry/setup.py b/packages/google-cloud-productregistry/setup.py new file mode 100644 index 000000000000..e0d4386d7319 --- /dev/null +++ b/packages/google-cloud-productregistry/setup.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 io +import os +import re + +import setuptools # type: ignore + +package_root = os.path.abspath(os.path.dirname(__file__)) + +name = "google-cloud-productregistry" + + +description = "Google Cloud Productregistry API client library" + +version = None + +with open( + os.path.join(package_root, "google/cloud/productregistry/gapic_version.py") +) as fp: + version_candidates = re.findall( + r"(?<=\")\d+\.\d+\.\d+[^\"\s]*(?=\")", + fp.read(), + ) + assert len(version_candidates) == 1 + version = version_candidates[0] + +if version[0] == "0": + release_status = "Development Status :: 4 - Beta" +else: + release_status = "Development Status :: 5 - Production/Stable" + +dependencies = [ + "google-api-core[grpc] >= 2.24.2, <3.0.0", + # Exclude incompatible versions of `google-auth` + # See https://github.com/googleapis/google-cloud-python/issues/12364 + "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", + "grpcio >= 1.59.0, < 2.0.0", + "grpcio >= 1.75.1, < 2.0.0; python_version >= '3.14'", + "proto-plus >= 1.26.1, <2.0.0", + "protobuf >= 6.33.5, < 8.0.0", +] +extras = {} +url = "https://github.com/googleapis/google-cloud-python/tree/main/packages/google-cloud-productregistry" + +package_root = os.path.abspath(os.path.dirname(__file__)) + +readme_filename = os.path.join(package_root, "README.rst") +with io.open(readme_filename, encoding="utf-8") as readme_file: + readme = readme_file.read() + +packages = [ + package + for package in setuptools.find_namespace_packages() + if package.startswith("google") +] + +setuptools.setup( + name=name, + version=version, + description=description, + long_description=readme, + author="Google LLC", + author_email="googleapis-packages@google.com", + license="Apache-2.0", + url=url, + classifiers=[ + release_status, + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Operating System :: OS Independent", + "Topic :: Internet", + ], + platforms="Posix; MacOS X; Windows", + packages=packages, + python_requires=">=3.10", + install_requires=dependencies, + extras_require=extras, + include_package_data=True, + zip_safe=False, +) diff --git a/packages/google-cloud-productregistry/testing/constraints-3.10.txt b/packages/google-cloud-productregistry/testing/constraints-3.10.txt new file mode 100644 index 000000000000..81605a716d32 --- /dev/null +++ b/packages/google-cloud-productregistry/testing/constraints-3.10.txt @@ -0,0 +1,11 @@ +# This constraints file is used to check that lower bounds +# are correct in setup.py +# List all library dependencies and extras in this file, +# pinning their versions to their lower bounds. +# For example, if setup.py has "google-cloud-foo >= 1.14.0, < 2.0.0", +# then this file should have google-cloud-foo==1.14.0 +google-api-core==2.24.2 +google-auth==2.14.1 +grpcio==1.59.0 +proto-plus==1.26.1 +protobuf==6.33.5 diff --git a/packages/google-cloud-productregistry/testing/constraints-3.11.txt b/packages/google-cloud-productregistry/testing/constraints-3.11.txt new file mode 100644 index 000000000000..7599dea499ed --- /dev/null +++ b/packages/google-cloud-productregistry/testing/constraints-3.11.txt @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# This constraints file is required for unit tests. +# List all library dependencies and extras in this file. +google-api-core +google-auth +grpcio +proto-plus +protobuf +# cryptography is a direct dependency of google-auth +cryptography diff --git a/packages/google-cloud-productregistry/testing/constraints-3.12.txt b/packages/google-cloud-productregistry/testing/constraints-3.12.txt new file mode 100644 index 000000000000..7599dea499ed --- /dev/null +++ b/packages/google-cloud-productregistry/testing/constraints-3.12.txt @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# This constraints file is required for unit tests. +# List all library dependencies and extras in this file. +google-api-core +google-auth +grpcio +proto-plus +protobuf +# cryptography is a direct dependency of google-auth +cryptography diff --git a/packages/google-cloud-productregistry/testing/constraints-3.13.txt b/packages/google-cloud-productregistry/testing/constraints-3.13.txt new file mode 100644 index 000000000000..6bd7e1f5b03d --- /dev/null +++ b/packages/google-cloud-productregistry/testing/constraints-3.13.txt @@ -0,0 +1,12 @@ +# We use the constraints file for the latest Python version +# (currently this file) to check that the latest +# major versions of dependencies are supported in setup.py. +# List all library dependencies and extras in this file. +# Require the latest major version be installed for each dependency. +# e.g., if setup.py has "google-cloud-foo >= 1.14.0, < 2.0.0", +# Then this file should have google-cloud-foo>=1 +google-api-core>=2 +google-auth>=2 +grpcio>=1 +proto-plus>=1 +protobuf>=7 diff --git a/packages/google-cloud-productregistry/testing/constraints-3.14.txt b/packages/google-cloud-productregistry/testing/constraints-3.14.txt new file mode 100644 index 000000000000..6bd7e1f5b03d --- /dev/null +++ b/packages/google-cloud-productregistry/testing/constraints-3.14.txt @@ -0,0 +1,12 @@ +# We use the constraints file for the latest Python version +# (currently this file) to check that the latest +# major versions of dependencies are supported in setup.py. +# List all library dependencies and extras in this file. +# Require the latest major version be installed for each dependency. +# e.g., if setup.py has "google-cloud-foo >= 1.14.0, < 2.0.0", +# Then this file should have google-cloud-foo>=1 +google-api-core>=2 +google-auth>=2 +grpcio>=1 +proto-plus>=1 +protobuf>=7 diff --git a/packages/google-cloud-productregistry/testing/constraints-3.15.txt b/packages/google-cloud-productregistry/testing/constraints-3.15.txt new file mode 100644 index 000000000000..6bd7e1f5b03d --- /dev/null +++ b/packages/google-cloud-productregistry/testing/constraints-3.15.txt @@ -0,0 +1,12 @@ +# We use the constraints file for the latest Python version +# (currently this file) to check that the latest +# major versions of dependencies are supported in setup.py. +# List all library dependencies and extras in this file. +# Require the latest major version be installed for each dependency. +# e.g., if setup.py has "google-cloud-foo >= 1.14.0, < 2.0.0", +# Then this file should have google-cloud-foo>=1 +google-api-core>=2 +google-auth>=2 +grpcio>=1 +proto-plus>=1 +protobuf>=7 diff --git a/packages/google-cloud-productregistry/tests/__init__.py b/packages/google-cloud-productregistry/tests/__init__.py new file mode 100644 index 000000000000..32b36c5c4fe0 --- /dev/null +++ b/packages/google-cloud-productregistry/tests/__init__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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-cloud-productregistry/tests/unit/__init__.py b/packages/google-cloud-productregistry/tests/unit/__init__.py new file mode 100644 index 000000000000..32b36c5c4fe0 --- /dev/null +++ b/packages/google-cloud-productregistry/tests/unit/__init__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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-cloud-productregistry/tests/unit/gapic/__init__.py b/packages/google-cloud-productregistry/tests/unit/gapic/__init__.py new file mode 100644 index 000000000000..32b36c5c4fe0 --- /dev/null +++ b/packages/google-cloud-productregistry/tests/unit/gapic/__init__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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-cloud-productregistry/tests/unit/gapic/productregistry_v1/__init__.py b/packages/google-cloud-productregistry/tests/unit/gapic/productregistry_v1/__init__.py new file mode 100644 index 000000000000..32b36c5c4fe0 --- /dev/null +++ b/packages/google-cloud-productregistry/tests/unit/gapic/productregistry_v1/__init__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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-cloud-productregistry/tests/unit/gapic/productregistry_v1/test_cloud_product_registry_read_service.py b/packages/google-cloud-productregistry/tests/unit/gapic/productregistry_v1/test_cloud_product_registry_read_service.py new file mode 100644 index 000000000000..2ca8124efa4c --- /dev/null +++ b/packages/google-cloud-productregistry/tests/unit/gapic/productregistry_v1/test_cloud_product_registry_read_service.py @@ -0,0 +1,7765 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 asyncio +import json +import math +import os +from collections.abc import AsyncIterable, Iterable, Mapping, Sequence +from unittest import mock +from unittest.mock import AsyncMock + +import grpc +import pytest +from google.api_core import api_core_version +from google.protobuf import json_format +from grpc.experimental import aio +from proto.marshal.rules import wrappers +from proto.marshal.rules.dates import DurationRule, TimestampRule +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 + +import google.auth +from google.api_core import ( + client_options, + gapic_v1, + grpc_helpers, + grpc_helpers_async, + path_template, +) +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.auth import credentials as ga_credentials +from google.auth.exceptions import MutualTLSChannelError +from google.oauth2 import service_account + +from google.cloud.productregistry_v1.services.cloud_product_registry_read_service import ( + CloudProductRegistryReadServiceAsyncClient, + CloudProductRegistryReadServiceClient, + pagers, + transports, +) +from google.cloud.productregistry_v1.types import ( + cloud_product_registry_read_service, + lifecycle_state, + logical_product, + logical_product_variant, + product_suite, +) + +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 + ) + + +@pytest.fixture(autouse=True) +def set_event_loop(): + try: + asyncio.get_running_loop() + yield + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + try: + yield + finally: + loop.close() + asyncio.set_event_loop(None) + + +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" + custom_endpoint = ".custom" + + assert ( + CloudProductRegistryReadServiceClient._get_default_mtls_endpoint(None) is None + ) + assert ( + CloudProductRegistryReadServiceClient._get_default_mtls_endpoint(api_endpoint) + == api_mtls_endpoint + ) + assert ( + CloudProductRegistryReadServiceClient._get_default_mtls_endpoint( + api_mtls_endpoint + ) + == api_mtls_endpoint + ) + assert ( + CloudProductRegistryReadServiceClient._get_default_mtls_endpoint( + sandbox_endpoint + ) + == sandbox_mtls_endpoint + ) + assert ( + CloudProductRegistryReadServiceClient._get_default_mtls_endpoint( + sandbox_mtls_endpoint + ) + == sandbox_mtls_endpoint + ) + assert ( + CloudProductRegistryReadServiceClient._get_default_mtls_endpoint(non_googleapi) + == non_googleapi + ) + assert ( + CloudProductRegistryReadServiceClient._get_default_mtls_endpoint( + custom_endpoint + ) + == custom_endpoint + ) + + +def test__read_environment_variables(): + assert CloudProductRegistryReadServiceClient._read_environment_variables() == ( + False, + "auto", + None, + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + assert CloudProductRegistryReadServiceClient._read_environment_variables() == ( + True, + "auto", + None, + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): + assert CloudProductRegistryReadServiceClient._read_environment_variables() == ( + False, + "auto", + None, + ) + + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with pytest.raises(ValueError) as excinfo: + CloudProductRegistryReadServiceClient._read_environment_variables() + assert ( + str(excinfo.value) + == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) + else: + assert ( + CloudProductRegistryReadServiceClient._read_environment_variables() + == ( + False, + "auto", + None, + ) + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + assert CloudProductRegistryReadServiceClient._read_environment_variables() == ( + False, + "never", + None, + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + assert CloudProductRegistryReadServiceClient._read_environment_variables() == ( + False, + "always", + None, + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}): + assert CloudProductRegistryReadServiceClient._read_environment_variables() == ( + False, + "auto", + None, + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError) as excinfo: + CloudProductRegistryReadServiceClient._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 CloudProductRegistryReadServiceClient._read_environment_variables() == ( + False, + "auto", + "foo.com", + ) + + +def test_use_client_cert_effective(): + # Test case 1: Test when `should_use_client_cert` returns True. + # We mock the `should_use_client_cert` function to simulate a scenario where + # the google-auth library supports automatic mTLS and determines that a + # client certificate should be used. + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch( + "google.auth.transport.mtls.should_use_client_cert", return_value=True + ): + assert ( + CloudProductRegistryReadServiceClient._use_client_cert_effective() + is True + ) + + # Test case 2: Test when `should_use_client_cert` returns False. + # We mock the `should_use_client_cert` function to simulate a scenario where + # the google-auth library supports automatic mTLS and determines that a + # client certificate should NOT be used. + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch( + "google.auth.transport.mtls.should_use_client_cert", return_value=False + ): + assert ( + CloudProductRegistryReadServiceClient._use_client_cert_effective() + is False + ) + + # Test case 3: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + assert ( + CloudProductRegistryReadServiceClient._use_client_cert_effective() + is True + ) + + # Test case 4: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "false". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"} + ): + assert ( + CloudProductRegistryReadServiceClient._use_client_cert_effective() + is False + ) + + # Test case 5: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "True". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "True"}): + assert ( + CloudProductRegistryReadServiceClient._use_client_cert_effective() + is True + ) + + # Test case 6: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "False". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "False"} + ): + assert ( + CloudProductRegistryReadServiceClient._use_client_cert_effective() + is False + ) + + # Test case 7: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "TRUE". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "TRUE"}): + assert ( + CloudProductRegistryReadServiceClient._use_client_cert_effective() + is True + ) + + # Test case 8: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "FALSE". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "FALSE"} + ): + assert ( + CloudProductRegistryReadServiceClient._use_client_cert_effective() + is False + ) + + # Test case 9: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not set. + # In this case, the method should return False, which is the default value. + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, clear=True): + assert ( + CloudProductRegistryReadServiceClient._use_client_cert_effective() + is False + ) + + # Test case 10: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. + # The method should raise a ValueError as the environment variable must be either + # "true" or "false". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"} + ): + with pytest.raises(ValueError): + CloudProductRegistryReadServiceClient._use_client_cert_effective() + + # Test case 11: Test when `should_use_client_cert` is available and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. + # The method should return False as the environment variable is set to an invalid value. + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"} + ): + assert ( + CloudProductRegistryReadServiceClient._use_client_cert_effective() + is False + ) + + # Test case 12: Test when `should_use_client_cert` is available and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also, + # the GOOGLE_API_CONFIG environment variable is unset. + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": ""}): + with mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}): + assert ( + CloudProductRegistryReadServiceClient._use_client_cert_effective() + is False + ) + + +def test__get_client_cert_source(): + mock_provided_cert_source = mock.Mock() + mock_default_cert_source = mock.Mock() + + assert ( + CloudProductRegistryReadServiceClient._get_client_cert_source(None, False) + is None + ) + assert ( + CloudProductRegistryReadServiceClient._get_client_cert_source( + mock_provided_cert_source, False + ) + is None + ) + assert ( + CloudProductRegistryReadServiceClient._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 ( + CloudProductRegistryReadServiceClient._get_client_cert_source( + None, True + ) + is mock_default_cert_source + ) + assert ( + CloudProductRegistryReadServiceClient._get_client_cert_source( + mock_provided_cert_source, "true" + ) + is mock_provided_cert_source + ) + + +@mock.patch.object( + CloudProductRegistryReadServiceClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(CloudProductRegistryReadServiceClient), +) +@mock.patch.object( + CloudProductRegistryReadServiceAsyncClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(CloudProductRegistryReadServiceAsyncClient), +) +def test__get_api_endpoint(): + api_override = "foo.com" + mock_client_cert_source = mock.Mock() + default_universe = CloudProductRegistryReadServiceClient._DEFAULT_UNIVERSE + default_endpoint = ( + CloudProductRegistryReadServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=default_universe + ) + ) + mock_universe = "bar.com" + mock_endpoint = ( + CloudProductRegistryReadServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=mock_universe + ) + ) + + assert ( + CloudProductRegistryReadServiceClient._get_api_endpoint( + api_override, mock_client_cert_source, default_universe, "always" + ) + == api_override + ) + assert ( + CloudProductRegistryReadServiceClient._get_api_endpoint( + None, mock_client_cert_source, default_universe, "auto" + ) + == CloudProductRegistryReadServiceClient.DEFAULT_MTLS_ENDPOINT + ) + assert ( + CloudProductRegistryReadServiceClient._get_api_endpoint( + None, None, default_universe, "auto" + ) + == default_endpoint + ) + assert ( + CloudProductRegistryReadServiceClient._get_api_endpoint( + None, None, default_universe, "always" + ) + == CloudProductRegistryReadServiceClient.DEFAULT_MTLS_ENDPOINT + ) + assert ( + CloudProductRegistryReadServiceClient._get_api_endpoint( + None, mock_client_cert_source, default_universe, "always" + ) + == CloudProductRegistryReadServiceClient.DEFAULT_MTLS_ENDPOINT + ) + assert ( + CloudProductRegistryReadServiceClient._get_api_endpoint( + None, None, mock_universe, "never" + ) + == mock_endpoint + ) + assert ( + CloudProductRegistryReadServiceClient._get_api_endpoint( + None, None, default_universe, "never" + ) + == default_endpoint + ) + + with pytest.raises(MutualTLSChannelError) as excinfo: + CloudProductRegistryReadServiceClient._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 ( + CloudProductRegistryReadServiceClient._get_universe_domain( + client_universe_domain, universe_domain_env + ) + == client_universe_domain + ) + assert ( + CloudProductRegistryReadServiceClient._get_universe_domain( + None, universe_domain_env + ) + == universe_domain_env + ) + assert ( + CloudProductRegistryReadServiceClient._get_universe_domain(None, None) + == CloudProductRegistryReadServiceClient._DEFAULT_UNIVERSE + ) + + with pytest.raises(ValueError) as excinfo: + CloudProductRegistryReadServiceClient._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 = CloudProductRegistryReadServiceClient(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 = CloudProductRegistryReadServiceClient(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", + [ + (CloudProductRegistryReadServiceClient, "grpc"), + (CloudProductRegistryReadServiceAsyncClient, "grpc_asyncio"), + (CloudProductRegistryReadServiceClient, "rest"), + ], +) +def test_cloud_product_registry_read_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 == ( + "cloudproductregistry.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://cloudproductregistry.googleapis.com" + ) + + +@pytest.mark.parametrize( + "transport_class,transport_name", + [ + (transports.CloudProductRegistryReadServiceGrpcTransport, "grpc"), + ( + transports.CloudProductRegistryReadServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + (transports.CloudProductRegistryReadServiceRestTransport, "rest"), + ], +) +def test_cloud_product_registry_read_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", + [ + (CloudProductRegistryReadServiceClient, "grpc"), + (CloudProductRegistryReadServiceAsyncClient, "grpc_asyncio"), + (CloudProductRegistryReadServiceClient, "rest"), + ], +) +def test_cloud_product_registry_read_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 == ( + "cloudproductregistry.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://cloudproductregistry.googleapis.com" + ) + + +def test_cloud_product_registry_read_service_client_get_transport_class(): + transport = CloudProductRegistryReadServiceClient.get_transport_class() + available_transports = [ + transports.CloudProductRegistryReadServiceGrpcTransport, + transports.CloudProductRegistryReadServiceRestTransport, + ] + assert transport in available_transports + + transport = CloudProductRegistryReadServiceClient.get_transport_class("grpc") + assert transport == transports.CloudProductRegistryReadServiceGrpcTransport + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + ( + CloudProductRegistryReadServiceClient, + transports.CloudProductRegistryReadServiceGrpcTransport, + "grpc", + ), + ( + CloudProductRegistryReadServiceAsyncClient, + transports.CloudProductRegistryReadServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ( + CloudProductRegistryReadServiceClient, + transports.CloudProductRegistryReadServiceRestTransport, + "rest", + ), + ], +) +@mock.patch.object( + CloudProductRegistryReadServiceClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(CloudProductRegistryReadServiceClient), +) +@mock.patch.object( + CloudProductRegistryReadServiceAsyncClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(CloudProductRegistryReadServiceAsyncClient), +) +def test_cloud_product_registry_read_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( + CloudProductRegistryReadServiceClient, "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( + CloudProductRegistryReadServiceClient, "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 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", + [ + ( + CloudProductRegistryReadServiceClient, + transports.CloudProductRegistryReadServiceGrpcTransport, + "grpc", + "true", + ), + ( + CloudProductRegistryReadServiceAsyncClient, + transports.CloudProductRegistryReadServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + ( + CloudProductRegistryReadServiceClient, + transports.CloudProductRegistryReadServiceGrpcTransport, + "grpc", + "false", + ), + ( + CloudProductRegistryReadServiceAsyncClient, + transports.CloudProductRegistryReadServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ( + CloudProductRegistryReadServiceClient, + transports.CloudProductRegistryReadServiceRestTransport, + "rest", + "true", + ), + ( + CloudProductRegistryReadServiceClient, + transports.CloudProductRegistryReadServiceRestTransport, + "rest", + "false", + ), + ], +) +@mock.patch.object( + CloudProductRegistryReadServiceClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(CloudProductRegistryReadServiceClient), +) +@mock.patch.object( + CloudProductRegistryReadServiceAsyncClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(CloudProductRegistryReadServiceAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_cloud_product_registry_read_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", + [CloudProductRegistryReadServiceClient, CloudProductRegistryReadServiceAsyncClient], +) +@mock.patch.object( + CloudProductRegistryReadServiceClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(CloudProductRegistryReadServiceClient), +) +@mock.patch.object( + CloudProductRegistryReadServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(CloudProductRegistryReadServiceAsyncClient), +) +def test_cloud_product_registry_read_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_CLIENT_CERTIFICATE is "Unsupported". + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + 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 cases for mTLS enablement when GOOGLE_API_USE_CLIENT_CERTIFICATE is unset. + test_cases = [ + ( + # With workloads present in config, mTLS is enabled. + { + "version": 1, + "cert_configs": { + "workload": { + "cert_path": "path/to/cert/file", + "key_path": "path/to/key/file", + } + }, + }, + mock_client_cert_source, + ), + ( + # With workloads not present in config, mTLS is disabled. + { + "version": 1, + "cert_configs": {}, + }, + None, + ), + ] + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + for config_data, expected_cert_source in test_cases: + env = os.environ.copy() + env.pop("GOOGLE_API_USE_CLIENT_CERTIFICATE", None) + with mock.patch.dict(os.environ, env, clear=True): + config_filename = "mock_certificate_config.json" + config_file_content = json.dumps(config_data) + m = mock.mock_open(read_data=config_file_content) + with mock.patch("builtins.open", m): + with mock.patch.dict( + os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} + ): + 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 expected_cert_source + + # Test cases for mTLS enablement when GOOGLE_API_USE_CLIENT_CERTIFICATE is unset(empty). + test_cases = [ + ( + # With workloads present in config, mTLS is enabled. + { + "version": 1, + "cert_configs": { + "workload": { + "cert_path": "path/to/cert/file", + "key_path": "path/to/key/file", + } + }, + }, + mock_client_cert_source, + ), + ( + # With workloads not present in config, mTLS is disabled. + { + "version": 1, + "cert_configs": {}, + }, + None, + ), + ] + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + for config_data, expected_cert_source in test_cases: + env = os.environ.copy() + env.pop("GOOGLE_API_USE_CLIENT_CERTIFICATE", "") + with mock.patch.dict(os.environ, env, clear=True): + config_filename = "mock_certificate_config.json" + config_file_content = json.dumps(config_data) + m = mock.mock_open(read_data=config_file_content) + with mock.patch("builtins.open", m): + with mock.patch.dict( + os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} + ): + 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 expected_cert_source + + # 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`" + ) + + +@pytest.mark.parametrize( + "client_class", + [CloudProductRegistryReadServiceClient, CloudProductRegistryReadServiceAsyncClient], +) +@mock.patch.object( + CloudProductRegistryReadServiceClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(CloudProductRegistryReadServiceClient), +) +@mock.patch.object( + CloudProductRegistryReadServiceAsyncClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(CloudProductRegistryReadServiceAsyncClient), +) +def test_cloud_product_registry_read_service_client_client_api_endpoint(client_class): + mock_client_cert_source = client_cert_source_callback + api_override = "foo.com" + default_universe = CloudProductRegistryReadServiceClient._DEFAULT_UNIVERSE + default_endpoint = ( + CloudProductRegistryReadServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=default_universe + ) + ) + mock_universe = "bar.com" + mock_endpoint = ( + CloudProductRegistryReadServiceClient._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", + [ + ( + CloudProductRegistryReadServiceClient, + transports.CloudProductRegistryReadServiceGrpcTransport, + "grpc", + ), + ( + CloudProductRegistryReadServiceAsyncClient, + transports.CloudProductRegistryReadServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ( + CloudProductRegistryReadServiceClient, + transports.CloudProductRegistryReadServiceRestTransport, + "rest", + ), + ], +) +def test_cloud_product_registry_read_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", + [ + ( + CloudProductRegistryReadServiceClient, + transports.CloudProductRegistryReadServiceGrpcTransport, + "grpc", + grpc_helpers, + ), + ( + CloudProductRegistryReadServiceAsyncClient, + transports.CloudProductRegistryReadServiceGrpcAsyncIOTransport, + "grpc_asyncio", + grpc_helpers_async, + ), + ( + CloudProductRegistryReadServiceClient, + transports.CloudProductRegistryReadServiceRestTransport, + "rest", + None, + ), + ], +) +def test_cloud_product_registry_read_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_cloud_product_registry_read_service_client_client_options_from_dict(): + with mock.patch( + "google.cloud.productregistry_v1.services.cloud_product_registry_read_service.transports.CloudProductRegistryReadServiceGrpcTransport.__init__" + ) as grpc_transport: + grpc_transport.return_value = None + client = CloudProductRegistryReadServiceClient( + 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", + [ + ( + CloudProductRegistryReadServiceClient, + transports.CloudProductRegistryReadServiceGrpcTransport, + "grpc", + grpc_helpers, + ), + ( + CloudProductRegistryReadServiceAsyncClient, + transports.CloudProductRegistryReadServiceGrpcAsyncIOTransport, + "grpc_asyncio", + grpc_helpers_async, + ), + ], +) +def test_cloud_product_registry_read_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( + "cloudproductregistry.googleapis.com:443", + credentials=file_creds, + credentials_file=None, + quota_project_id=None, + default_scopes=(), + scopes=None, + default_host="cloudproductregistry.googleapis.com", + ssl_credentials=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.GetProductSuiteRequest(), + {}, + ], +) +def test_get_product_suite(request_type, transport: str = "grpc"): + client = CloudProductRegistryReadServiceClient( + 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_suite), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = product_suite.ProductSuite( + name="name_value", + title="title_value", + logical_products=["logical_products_value"], + replaced=True, + replacement="replacement_value", + ) + response = client.get_product_suite(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + request = cloud_product_registry_read_service.GetProductSuiteRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, product_suite.ProductSuite) + assert response.name == "name_value" + assert response.title == "title_value" + assert response.logical_products == ["logical_products_value"] + assert response.replaced is True + assert response.replacement == "replacement_value" + + +def test_get_product_suite_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 = CloudProductRegistryReadServiceClient( + 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 = cloud_product_registry_read_service.GetProductSuiteRequest( + name="name_value", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.get_product_suite), "__call__" + ) as call: + call.return_value.name = ( + "foo" # operation_request.operation in compute client(s) expect a string. + ) + client.get_product_suite(request=request) + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.GetProductSuiteRequest( + name="name_value", + ) + assert args[0] == request_msg + + +def test_get_product_suite_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 = CloudProductRegistryReadServiceClient( + 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_suite 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_suite] = ( + mock_rpc + ) + request = {} + client.get_product_suite(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.get_product_suite(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_suite_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 = CloudProductRegistryReadServiceAsyncClient( + 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_suite + 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_suite + ] = mock_rpc + + request = {} + await client.get_product_suite(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + await client.get_product_suite(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 +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.GetProductSuiteRequest(), + {}, + ], +) +async def test_get_product_suite_async(request_type, transport: str = "grpc_asyncio"): + client = CloudProductRegistryReadServiceAsyncClient( + 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_suite), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + product_suite.ProductSuite( + name="name_value", + title="title_value", + logical_products=["logical_products_value"], + replaced=True, + replacement="replacement_value", + ) + ) + response = await client.get_product_suite(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + request = cloud_product_registry_read_service.GetProductSuiteRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, product_suite.ProductSuite) + assert response.name == "name_value" + assert response.title == "title_value" + assert response.logical_products == ["logical_products_value"] + assert response.replaced is True + assert response.replacement == "replacement_value" + + +def test_get_product_suite_field_headers(): + client = CloudProductRegistryReadServiceClient( + 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 = cloud_product_registry_read_service.GetProductSuiteRequest() + + 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_suite), "__call__" + ) as call: + call.return_value = product_suite.ProductSuite() + client.get_product_suite(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_suite_field_headers_async(): + client = CloudProductRegistryReadServiceAsyncClient( + 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 = cloud_product_registry_read_service.GetProductSuiteRequest() + + 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_suite), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + product_suite.ProductSuite() + ) + await client.get_product_suite(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_suite_flattened(): + client = CloudProductRegistryReadServiceClient( + 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_suite), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = product_suite.ProductSuite() + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.get_product_suite( + 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_suite_flattened_error(): + client = CloudProductRegistryReadServiceClient( + 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_suite( + cloud_product_registry_read_service.GetProductSuiteRequest(), + name="name_value", + ) + + +@pytest.mark.asyncio +async def test_get_product_suite_flattened_async(): + client = CloudProductRegistryReadServiceAsyncClient( + 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_suite), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = product_suite.ProductSuite() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + product_suite.ProductSuite() + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.get_product_suite( + 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_suite_flattened_error_async(): + client = CloudProductRegistryReadServiceAsyncClient( + 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_suite( + cloud_product_registry_read_service.GetProductSuiteRequest(), + name="name_value", + ) + + +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.ListProductSuitesRequest(), + {}, + ], +) +def test_list_product_suites(request_type, transport: str = "grpc"): + client = CloudProductRegistryReadServiceClient( + 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_product_suites), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = ( + cloud_product_registry_read_service.ListProductSuitesResponse( + next_page_token="next_page_token_value", + ) + ) + response = client.list_product_suites(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + request = cloud_product_registry_read_service.ListProductSuitesRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListProductSuitesPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_product_suites_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 = CloudProductRegistryReadServiceClient( + 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 = cloud_product_registry_read_service.ListProductSuitesRequest( + 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_product_suites), "__call__" + ) as call: + call.return_value.name = ( + "foo" # operation_request.operation in compute client(s) expect a string. + ) + client.list_product_suites(request=request) + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.ListProductSuitesRequest( + page_token="page_token_value", + ) + assert args[0] == request_msg + + +def test_list_product_suites_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 = CloudProductRegistryReadServiceClient( + 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_product_suites 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_product_suites] = ( + mock_rpc + ) + request = {} + client.list_product_suites(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.list_product_suites(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_product_suites_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 = CloudProductRegistryReadServiceAsyncClient( + 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_product_suites + 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_product_suites + ] = mock_rpc + + request = {} + await client.list_product_suites(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + await client.list_product_suites(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 +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.ListProductSuitesRequest(), + {}, + ], +) +async def test_list_product_suites_async(request_type, transport: str = "grpc_asyncio"): + client = CloudProductRegistryReadServiceAsyncClient( + 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_product_suites), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + cloud_product_registry_read_service.ListProductSuitesResponse( + next_page_token="next_page_token_value", + ) + ) + response = await client.list_product_suites(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + request = cloud_product_registry_read_service.ListProductSuitesRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListProductSuitesAsyncPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_product_suites_pager(transport_name: str = "grpc"): + client = CloudProductRegistryReadServiceClient( + 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_product_suites), "__call__" + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[ + product_suite.ProductSuite(), + product_suite.ProductSuite(), + product_suite.ProductSuite(), + ], + next_page_token="abc", + ), + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[], + next_page_token="def", + ), + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[ + product_suite.ProductSuite(), + ], + next_page_token="ghi", + ), + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[ + product_suite.ProductSuite(), + product_suite.ProductSuite(), + ], + ), + RuntimeError, + ) + + expected_metadata = () + retry = retries.Retry() + timeout = 5 + pager = client.list_product_suites(request={}, retry=retry, timeout=timeout) + + assert pager._metadata == expected_metadata + assert pager._retry == retry + assert pager._timeout == timeout + + assert pager.next_page_token == "abc" + assert str(pager).startswith(f"{pager.__class__.__name__}<") + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, product_suite.ProductSuite) for i in results) + + +def test_list_product_suites_pages(transport_name: str = "grpc"): + client = CloudProductRegistryReadServiceClient( + 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_product_suites), "__call__" + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[ + product_suite.ProductSuite(), + product_suite.ProductSuite(), + product_suite.ProductSuite(), + ], + next_page_token="abc", + ), + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[], + next_page_token="def", + ), + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[ + product_suite.ProductSuite(), + ], + next_page_token="ghi", + ), + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[ + product_suite.ProductSuite(), + product_suite.ProductSuite(), + ], + ), + RuntimeError, + ) + pages = list(client.list_product_suites(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_product_suites_async_pager(): + client = CloudProductRegistryReadServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_product_suites), + "__call__", + new_callable=mock.AsyncMock, + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[ + product_suite.ProductSuite(), + product_suite.ProductSuite(), + product_suite.ProductSuite(), + ], + next_page_token="abc", + ), + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[], + next_page_token="def", + ), + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[ + product_suite.ProductSuite(), + ], + next_page_token="ghi", + ), + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[ + product_suite.ProductSuite(), + product_suite.ProductSuite(), + ], + ), + RuntimeError, + ) + async_pager = await client.list_product_suites( + request={}, + ) + assert async_pager.next_page_token == "abc" + assert str(async_pager).startswith(f"{async_pager.__class__.__name__}<") + + responses = [] + async for response in async_pager: # pragma: no branch + responses.append(response) + + assert len(responses) == 6 + assert all(isinstance(i, product_suite.ProductSuite) for i in responses) + + +@pytest.mark.asyncio +async def test_list_product_suites_async_pages(): + client = CloudProductRegistryReadServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_product_suites), + "__call__", + new_callable=mock.AsyncMock, + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[ + product_suite.ProductSuite(), + product_suite.ProductSuite(), + product_suite.ProductSuite(), + ], + next_page_token="abc", + ), + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[], + next_page_token="def", + ), + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[ + product_suite.ProductSuite(), + ], + next_page_token="ghi", + ), + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[ + product_suite.ProductSuite(), + product_suite.ProductSuite(), + ], + ), + RuntimeError, + ) + pages = [] + async for page_ in (await client.list_product_suites(request={})).pages: + pages.append(page_) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.GetLogicalProductRequest(), + {}, + ], +) +def test_get_logical_product(request_type, transport: str = "grpc"): + client = CloudProductRegistryReadServiceClient( + 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_logical_product), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = logical_product.LogicalProduct( + name="name_value", + title="title_value", + product_suite="product_suite_value", + variants=["variants_value"], + lifecycle_state=lifecycle_state.LifecycleState.LIFECYCLE_STATE_PUBLIC_PREVIEW, + replaced=True, + replacement="replacement_value", + ) + response = client.get_logical_product(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + request = cloud_product_registry_read_service.GetLogicalProductRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, logical_product.LogicalProduct) + assert response.name == "name_value" + assert response.title == "title_value" + assert response.product_suite == "product_suite_value" + assert response.variants == ["variants_value"] + assert ( + response.lifecycle_state + == lifecycle_state.LifecycleState.LIFECYCLE_STATE_PUBLIC_PREVIEW + ) + assert response.replaced is True + assert response.replacement == "replacement_value" + + +def test_get_logical_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 = CloudProductRegistryReadServiceClient( + 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 = cloud_product_registry_read_service.GetLogicalProductRequest( + name="name_value", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.get_logical_product), "__call__" + ) as call: + call.return_value.name = ( + "foo" # operation_request.operation in compute client(s) expect a string. + ) + client.get_logical_product(request=request) + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.GetLogicalProductRequest( + name="name_value", + ) + assert args[0] == request_msg + + +def test_get_logical_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 = CloudProductRegistryReadServiceClient( + 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_logical_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_logical_product] = ( + mock_rpc + ) + request = {} + client.get_logical_product(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.get_logical_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_logical_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 = CloudProductRegistryReadServiceAsyncClient( + 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_logical_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_logical_product + ] = mock_rpc + + request = {} + await client.get_logical_product(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + await client.get_logical_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 +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.GetLogicalProductRequest(), + {}, + ], +) +async def test_get_logical_product_async(request_type, transport: str = "grpc_asyncio"): + client = CloudProductRegistryReadServiceAsyncClient( + 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_logical_product), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + logical_product.LogicalProduct( + name="name_value", + title="title_value", + product_suite="product_suite_value", + variants=["variants_value"], + lifecycle_state=lifecycle_state.LifecycleState.LIFECYCLE_STATE_PUBLIC_PREVIEW, + replaced=True, + replacement="replacement_value", + ) + ) + response = await client.get_logical_product(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + request = cloud_product_registry_read_service.GetLogicalProductRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, logical_product.LogicalProduct) + assert response.name == "name_value" + assert response.title == "title_value" + assert response.product_suite == "product_suite_value" + assert response.variants == ["variants_value"] + assert ( + response.lifecycle_state + == lifecycle_state.LifecycleState.LIFECYCLE_STATE_PUBLIC_PREVIEW + ) + assert response.replaced is True + assert response.replacement == "replacement_value" + + +def test_get_logical_product_field_headers(): + client = CloudProductRegistryReadServiceClient( + 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 = cloud_product_registry_read_service.GetLogicalProductRequest() + + request.name = "name_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.get_logical_product), "__call__" + ) as call: + call.return_value = logical_product.LogicalProduct() + client.get_logical_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_logical_product_field_headers_async(): + client = CloudProductRegistryReadServiceAsyncClient( + 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 = cloud_product_registry_read_service.GetLogicalProductRequest() + + request.name = "name_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.get_logical_product), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + logical_product.LogicalProduct() + ) + await client.get_logical_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_logical_product_flattened(): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.get_logical_product), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = logical_product.LogicalProduct() + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.get_logical_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_logical_product_flattened_error(): + client = CloudProductRegistryReadServiceClient( + 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_logical_product( + cloud_product_registry_read_service.GetLogicalProductRequest(), + name="name_value", + ) + + +@pytest.mark.asyncio +async def test_get_logical_product_flattened_async(): + client = CloudProductRegistryReadServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.get_logical_product), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = logical_product.LogicalProduct() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + logical_product.LogicalProduct() + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.get_logical_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_logical_product_flattened_error_async(): + client = CloudProductRegistryReadServiceAsyncClient( + 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_logical_product( + cloud_product_registry_read_service.GetLogicalProductRequest(), + name="name_value", + ) + + +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.ListLogicalProductsRequest(), + {}, + ], +) +def test_list_logical_products(request_type, transport: str = "grpc"): + client = CloudProductRegistryReadServiceClient( + 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_logical_products), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = ( + cloud_product_registry_read_service.ListLogicalProductsResponse( + next_page_token="next_page_token_value", + ) + ) + response = client.list_logical_products(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + request = cloud_product_registry_read_service.ListLogicalProductsRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListLogicalProductsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_logical_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 = CloudProductRegistryReadServiceClient( + 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 = cloud_product_registry_read_service.ListLogicalProductsRequest( + filter="filter_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_logical_products), "__call__" + ) as call: + call.return_value.name = ( + "foo" # operation_request.operation in compute client(s) expect a string. + ) + client.list_logical_products(request=request) + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.ListLogicalProductsRequest( + filter="filter_value", + page_token="page_token_value", + ) + assert args[0] == request_msg + + +def test_list_logical_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 = CloudProductRegistryReadServiceClient( + 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_logical_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_logical_products] = ( + mock_rpc + ) + request = {} + client.list_logical_products(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.list_logical_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_logical_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 = CloudProductRegistryReadServiceAsyncClient( + 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_logical_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_logical_products + ] = mock_rpc + + request = {} + await client.list_logical_products(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + await client.list_logical_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 +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.ListLogicalProductsRequest(), + {}, + ], +) +async def test_list_logical_products_async( + request_type, transport: str = "grpc_asyncio" +): + client = CloudProductRegistryReadServiceAsyncClient( + 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_logical_products), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + cloud_product_registry_read_service.ListLogicalProductsResponse( + next_page_token="next_page_token_value", + ) + ) + response = await client.list_logical_products(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + request = cloud_product_registry_read_service.ListLogicalProductsRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListLogicalProductsAsyncPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_logical_products_pager(transport_name: str = "grpc"): + client = CloudProductRegistryReadServiceClient( + 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_logical_products), "__call__" + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[ + logical_product.LogicalProduct(), + logical_product.LogicalProduct(), + logical_product.LogicalProduct(), + ], + next_page_token="abc", + ), + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[], + next_page_token="def", + ), + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[ + logical_product.LogicalProduct(), + ], + next_page_token="ghi", + ), + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[ + logical_product.LogicalProduct(), + logical_product.LogicalProduct(), + ], + ), + RuntimeError, + ) + + expected_metadata = () + retry = retries.Retry() + timeout = 5 + pager = client.list_logical_products(request={}, retry=retry, timeout=timeout) + + assert pager._metadata == expected_metadata + assert pager._retry == retry + assert pager._timeout == timeout + + assert pager.next_page_token == "abc" + assert str(pager).startswith(f"{pager.__class__.__name__}<") + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, logical_product.LogicalProduct) for i in results) + + +def test_list_logical_products_pages(transport_name: str = "grpc"): + client = CloudProductRegistryReadServiceClient( + 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_logical_products), "__call__" + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[ + logical_product.LogicalProduct(), + logical_product.LogicalProduct(), + logical_product.LogicalProduct(), + ], + next_page_token="abc", + ), + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[], + next_page_token="def", + ), + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[ + logical_product.LogicalProduct(), + ], + next_page_token="ghi", + ), + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[ + logical_product.LogicalProduct(), + logical_product.LogicalProduct(), + ], + ), + RuntimeError, + ) + pages = list(client.list_logical_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_logical_products_async_pager(): + client = CloudProductRegistryReadServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_logical_products), + "__call__", + new_callable=mock.AsyncMock, + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[ + logical_product.LogicalProduct(), + logical_product.LogicalProduct(), + logical_product.LogicalProduct(), + ], + next_page_token="abc", + ), + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[], + next_page_token="def", + ), + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[ + logical_product.LogicalProduct(), + ], + next_page_token="ghi", + ), + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[ + logical_product.LogicalProduct(), + logical_product.LogicalProduct(), + ], + ), + RuntimeError, + ) + async_pager = await client.list_logical_products( + request={}, + ) + assert async_pager.next_page_token == "abc" + assert str(async_pager).startswith(f"{async_pager.__class__.__name__}<") + + responses = [] + async for response in async_pager: # pragma: no branch + responses.append(response) + + assert len(responses) == 6 + assert all(isinstance(i, logical_product.LogicalProduct) for i in responses) + + +@pytest.mark.asyncio +async def test_list_logical_products_async_pages(): + client = CloudProductRegistryReadServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_logical_products), + "__call__", + new_callable=mock.AsyncMock, + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[ + logical_product.LogicalProduct(), + logical_product.LogicalProduct(), + logical_product.LogicalProduct(), + ], + next_page_token="abc", + ), + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[], + next_page_token="def", + ), + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[ + logical_product.LogicalProduct(), + ], + next_page_token="ghi", + ), + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[ + logical_product.LogicalProduct(), + logical_product.LogicalProduct(), + ], + ), + RuntimeError, + ) + pages = [] + async for page_ in (await client.list_logical_products(request={})).pages: + pages.append(page_) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.GetLogicalProductVariantRequest(), + {}, + ], +) +def test_get_logical_product_variant(request_type, transport: str = "grpc"): + client = CloudProductRegistryReadServiceClient( + 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_logical_product_variant), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = logical_product_variant.LogicalProductVariant( + name="name_value", + title="title_value", + lifecycle_state=lifecycle_state.LifecycleState.LIFECYCLE_STATE_PUBLIC_PREVIEW, + replaced=True, + replacement="replacement_value", + ) + response = client.get_logical_product_variant(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + request = cloud_product_registry_read_service.GetLogicalProductVariantRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, logical_product_variant.LogicalProductVariant) + assert response.name == "name_value" + assert response.title == "title_value" + assert ( + response.lifecycle_state + == lifecycle_state.LifecycleState.LIFECYCLE_STATE_PUBLIC_PREVIEW + ) + assert response.replaced is True + assert response.replacement == "replacement_value" + + +def test_get_logical_product_variant_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 = CloudProductRegistryReadServiceClient( + 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 = cloud_product_registry_read_service.GetLogicalProductVariantRequest( + name="name_value", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.get_logical_product_variant), "__call__" + ) as call: + call.return_value.name = ( + "foo" # operation_request.operation in compute client(s) expect a string. + ) + client.get_logical_product_variant(request=request) + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = ( + cloud_product_registry_read_service.GetLogicalProductVariantRequest( + name="name_value", + ) + ) + assert args[0] == request_msg + + +def test_get_logical_product_variant_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 = CloudProductRegistryReadServiceClient( + 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_logical_product_variant + 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_logical_product_variant + ] = mock_rpc + request = {} + client.get_logical_product_variant(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.get_logical_product_variant(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_logical_product_variant_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 = CloudProductRegistryReadServiceAsyncClient( + 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_logical_product_variant + 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_logical_product_variant + ] = mock_rpc + + request = {} + await client.get_logical_product_variant(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + await client.get_logical_product_variant(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 +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.GetLogicalProductVariantRequest(), + {}, + ], +) +async def test_get_logical_product_variant_async( + request_type, transport: str = "grpc_asyncio" +): + client = CloudProductRegistryReadServiceAsyncClient( + 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_logical_product_variant), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + logical_product_variant.LogicalProductVariant( + name="name_value", + title="title_value", + lifecycle_state=lifecycle_state.LifecycleState.LIFECYCLE_STATE_PUBLIC_PREVIEW, + replaced=True, + replacement="replacement_value", + ) + ) + response = await client.get_logical_product_variant(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + request = cloud_product_registry_read_service.GetLogicalProductVariantRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, logical_product_variant.LogicalProductVariant) + assert response.name == "name_value" + assert response.title == "title_value" + assert ( + response.lifecycle_state + == lifecycle_state.LifecycleState.LIFECYCLE_STATE_PUBLIC_PREVIEW + ) + assert response.replaced is True + assert response.replacement == "replacement_value" + + +def test_get_logical_product_variant_field_headers(): + client = CloudProductRegistryReadServiceClient( + 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 = cloud_product_registry_read_service.GetLogicalProductVariantRequest() + + request.name = "name_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.get_logical_product_variant), "__call__" + ) as call: + call.return_value = logical_product_variant.LogicalProductVariant() + client.get_logical_product_variant(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_logical_product_variant_field_headers_async(): + client = CloudProductRegistryReadServiceAsyncClient( + 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 = cloud_product_registry_read_service.GetLogicalProductVariantRequest() + + request.name = "name_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.get_logical_product_variant), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + logical_product_variant.LogicalProductVariant() + ) + await client.get_logical_product_variant(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_logical_product_variant_flattened(): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.get_logical_product_variant), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = logical_product_variant.LogicalProductVariant() + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.get_logical_product_variant( + 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_logical_product_variant_flattened_error(): + client = CloudProductRegistryReadServiceClient( + 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_logical_product_variant( + cloud_product_registry_read_service.GetLogicalProductVariantRequest(), + name="name_value", + ) + + +@pytest.mark.asyncio +async def test_get_logical_product_variant_flattened_async(): + client = CloudProductRegistryReadServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.get_logical_product_variant), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = logical_product_variant.LogicalProductVariant() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + logical_product_variant.LogicalProductVariant() + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.get_logical_product_variant( + 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_logical_product_variant_flattened_error_async(): + client = CloudProductRegistryReadServiceAsyncClient( + 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_logical_product_variant( + cloud_product_registry_read_service.GetLogicalProductVariantRequest(), + name="name_value", + ) + + +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.ListLogicalProductVariantsRequest(), + {}, + ], +) +def test_list_logical_product_variants(request_type, transport: str = "grpc"): + client = CloudProductRegistryReadServiceClient( + 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_logical_product_variants), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = ( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + next_page_token="next_page_token_value", + ) + ) + response = client.list_logical_product_variants(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + request = ( + cloud_product_registry_read_service.ListLogicalProductVariantsRequest() + ) + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListLogicalProductVariantsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_logical_product_variants_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 = CloudProductRegistryReadServiceClient( + 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 = cloud_product_registry_read_service.ListLogicalProductVariantsRequest( + 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_logical_product_variants), "__call__" + ) as call: + call.return_value.name = ( + "foo" # operation_request.operation in compute client(s) expect a string. + ) + client.list_logical_product_variants(request=request) + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = ( + cloud_product_registry_read_service.ListLogicalProductVariantsRequest( + parent="parent_value", + page_token="page_token_value", + ) + ) + assert args[0] == request_msg + + +def test_list_logical_product_variants_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 = CloudProductRegistryReadServiceClient( + 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_logical_product_variants + 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_logical_product_variants + ] = mock_rpc + request = {} + client.list_logical_product_variants(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.list_logical_product_variants(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_logical_product_variants_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 = CloudProductRegistryReadServiceAsyncClient( + 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_logical_product_variants + 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_logical_product_variants + ] = mock_rpc + + request = {} + await client.list_logical_product_variants(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + await client.list_logical_product_variants(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 +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.ListLogicalProductVariantsRequest(), + {}, + ], +) +async def test_list_logical_product_variants_async( + request_type, transport: str = "grpc_asyncio" +): + client = CloudProductRegistryReadServiceAsyncClient( + 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_logical_product_variants), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + next_page_token="next_page_token_value", + ) + ) + response = await client.list_logical_product_variants(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + request = ( + cloud_product_registry_read_service.ListLogicalProductVariantsRequest() + ) + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListLogicalProductVariantsAsyncPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_logical_product_variants_field_headers(): + client = CloudProductRegistryReadServiceClient( + 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 = cloud_product_registry_read_service.ListLogicalProductVariantsRequest() + + request.parent = "parent_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_logical_product_variants), "__call__" + ) as call: + call.return_value = ( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse() + ) + client.list_logical_product_variants(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_logical_product_variants_field_headers_async(): + client = CloudProductRegistryReadServiceAsyncClient( + 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 = cloud_product_registry_read_service.ListLogicalProductVariantsRequest() + + request.parent = "parent_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_logical_product_variants), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse() + ) + await client.list_logical_product_variants(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_logical_product_variants_flattened(): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_logical_product_variants), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = ( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse() + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.list_logical_product_variants( + 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_logical_product_variants_flattened_error(): + client = CloudProductRegistryReadServiceClient( + 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_logical_product_variants( + cloud_product_registry_read_service.ListLogicalProductVariantsRequest(), + parent="parent_value", + ) + + +@pytest.mark.asyncio +async def test_list_logical_product_variants_flattened_async(): + client = CloudProductRegistryReadServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_logical_product_variants), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = ( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse() + ) + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse() + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.list_logical_product_variants( + 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_logical_product_variants_flattened_error_async(): + client = CloudProductRegistryReadServiceAsyncClient( + 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_logical_product_variants( + cloud_product_registry_read_service.ListLogicalProductVariantsRequest(), + parent="parent_value", + ) + + +def test_list_logical_product_variants_pager(transport_name: str = "grpc"): + client = CloudProductRegistryReadServiceClient( + 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_logical_product_variants), "__call__" + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[ + logical_product_variant.LogicalProductVariant(), + logical_product_variant.LogicalProductVariant(), + logical_product_variant.LogicalProductVariant(), + ], + next_page_token="abc", + ), + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[], + next_page_token="def", + ), + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[ + logical_product_variant.LogicalProductVariant(), + ], + next_page_token="ghi", + ), + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[ + logical_product_variant.LogicalProductVariant(), + logical_product_variant.LogicalProductVariant(), + ], + ), + RuntimeError, + ) + + expected_metadata = () + retry = retries.Retry() + timeout = 5 + expected_metadata = tuple(expected_metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", ""),)), + ) + pager = client.list_logical_product_variants( + request={}, retry=retry, timeout=timeout + ) + + assert pager._metadata == expected_metadata + assert pager._retry == retry + assert pager._timeout == timeout + + assert pager.next_page_token == "abc" + assert str(pager).startswith(f"{pager.__class__.__name__}<") + + results = list(pager) + assert len(results) == 6 + assert all( + isinstance(i, logical_product_variant.LogicalProductVariant) + for i in results + ) + + +def test_list_logical_product_variants_pages(transport_name: str = "grpc"): + client = CloudProductRegistryReadServiceClient( + 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_logical_product_variants), "__call__" + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[ + logical_product_variant.LogicalProductVariant(), + logical_product_variant.LogicalProductVariant(), + logical_product_variant.LogicalProductVariant(), + ], + next_page_token="abc", + ), + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[], + next_page_token="def", + ), + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[ + logical_product_variant.LogicalProductVariant(), + ], + next_page_token="ghi", + ), + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[ + logical_product_variant.LogicalProductVariant(), + logical_product_variant.LogicalProductVariant(), + ], + ), + RuntimeError, + ) + pages = list(client.list_logical_product_variants(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_logical_product_variants_async_pager(): + client = CloudProductRegistryReadServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_logical_product_variants), + "__call__", + new_callable=mock.AsyncMock, + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[ + logical_product_variant.LogicalProductVariant(), + logical_product_variant.LogicalProductVariant(), + logical_product_variant.LogicalProductVariant(), + ], + next_page_token="abc", + ), + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[], + next_page_token="def", + ), + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[ + logical_product_variant.LogicalProductVariant(), + ], + next_page_token="ghi", + ), + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[ + logical_product_variant.LogicalProductVariant(), + logical_product_variant.LogicalProductVariant(), + ], + ), + RuntimeError, + ) + async_pager = await client.list_logical_product_variants( + request={}, + ) + assert async_pager.next_page_token == "abc" + assert str(async_pager).startswith(f"{async_pager.__class__.__name__}<") + + responses = [] + async for response in async_pager: # pragma: no branch + responses.append(response) + + assert len(responses) == 6 + assert all( + isinstance(i, logical_product_variant.LogicalProductVariant) + for i in responses + ) + + +@pytest.mark.asyncio +async def test_list_logical_product_variants_async_pages(): + client = CloudProductRegistryReadServiceAsyncClient( + credentials=async_anonymous_credentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_logical_product_variants), + "__call__", + new_callable=mock.AsyncMock, + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[ + logical_product_variant.LogicalProductVariant(), + logical_product_variant.LogicalProductVariant(), + logical_product_variant.LogicalProductVariant(), + ], + next_page_token="abc", + ), + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[], + next_page_token="def", + ), + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[ + logical_product_variant.LogicalProductVariant(), + ], + next_page_token="ghi", + ), + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[ + logical_product_variant.LogicalProductVariant(), + logical_product_variant.LogicalProductVariant(), + ], + ), + RuntimeError, + ) + pages = [] + async for page_ in ( + await client.list_logical_product_variants(request={}) + ).pages: + pages.append(page_) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.LookupEntityRequest(), + {}, + ], +) +def test_lookup_entity(request_type, transport: str = "grpc"): + client = CloudProductRegistryReadServiceClient( + 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.lookup_entity), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = cloud_product_registry_read_service.LookupEntityResponse() + response = client.lookup_entity(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + request = cloud_product_registry_read_service.LookupEntityRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance( + response, cloud_product_registry_read_service.LookupEntityResponse + ) + + +def test_lookup_entity_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 = CloudProductRegistryReadServiceClient( + 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 = cloud_product_registry_read_service.LookupEntityRequest( + lookup_uri="lookup_uri_value", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.lookup_entity), "__call__") as call: + call.return_value.name = ( + "foo" # operation_request.operation in compute client(s) expect a string. + ) + client.lookup_entity(request=request) + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.LookupEntityRequest( + lookup_uri="lookup_uri_value", + ) + assert args[0] == request_msg + + +def test_lookup_entity_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 = CloudProductRegistryReadServiceClient( + 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.lookup_entity 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.lookup_entity] = mock_rpc + request = {} + client.lookup_entity(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.lookup_entity(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_lookup_entity_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 = CloudProductRegistryReadServiceAsyncClient( + 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.lookup_entity + 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.lookup_entity + ] = mock_rpc + + request = {} + await client.lookup_entity(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + await client.lookup_entity(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 +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.LookupEntityRequest(), + {}, + ], +) +async def test_lookup_entity_async(request_type, transport: str = "grpc_asyncio"): + client = CloudProductRegistryReadServiceAsyncClient( + 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.lookup_entity), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + cloud_product_registry_read_service.LookupEntityResponse() + ) + response = await client.lookup_entity(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + request = cloud_product_registry_read_service.LookupEntityRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance( + response, cloud_product_registry_read_service.LookupEntityResponse + ) + + +def test_lookup_entity_field_headers(): + client = CloudProductRegistryReadServiceClient( + 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 = cloud_product_registry_read_service.LookupEntityRequest() + + request.lookup_uri = "lookup_uri_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.lookup_entity), "__call__") as call: + call.return_value = cloud_product_registry_read_service.LookupEntityResponse() + client.lookup_entity(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", + "lookup_uri=lookup_uri_value", + ) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_lookup_entity_field_headers_async(): + client = CloudProductRegistryReadServiceAsyncClient( + 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 = cloud_product_registry_read_service.LookupEntityRequest() + + request.lookup_uri = "lookup_uri_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.lookup_entity), "__call__") as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + cloud_product_registry_read_service.LookupEntityResponse() + ) + await client.lookup_entity(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", + "lookup_uri=lookup_uri_value", + ) in kw["metadata"] + + +def test_get_product_suite_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 = CloudProductRegistryReadServiceClient( + 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_suite 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_suite] = ( + mock_rpc + ) + + request = {} + client.get_product_suite(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.get_product_suite(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_suite_rest_required_fields( + request_type=cloud_product_registry_read_service.GetProductSuiteRequest, +): + transport_class = transports.CloudProductRegistryReadServiceRestTransport + + 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_suite._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_suite._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 = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = product_suite.ProductSuite() + # 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 = product_suite.ProductSuite.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_suite(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert sorted(expected_params) == sorted(actual_params) + + +def test_get_product_suite_rest_unset_required_fields(): + transport = transports.CloudProductRegistryReadServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_product_suite._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +def test_get_product_suite_rest_flattened(): + client = CloudProductRegistryReadServiceClient( + 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 = product_suite.ProductSuite() + + # get arguments that satisfy an http rule for this method + sample_request = {"name": "productSuites/sample1"} + + # 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 = product_suite.ProductSuite.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_suite(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v1/{name=productSuites/*}" % client.transport._host, args[1] + ) + + +def test_get_product_suite_rest_flattened_error(transport: str = "rest"): + client = CloudProductRegistryReadServiceClient( + 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_suite( + cloud_product_registry_read_service.GetProductSuiteRequest(), + name="name_value", + ) + + +def test_list_product_suites_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 = CloudProductRegistryReadServiceClient( + 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_product_suites 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_product_suites] = ( + mock_rpc + ) + + request = {} + client.list_product_suites(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.list_product_suites(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_product_suites_rest_pager(transport: str = "rest"): + client = CloudProductRegistryReadServiceClient( + 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 = ( + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[ + product_suite.ProductSuite(), + product_suite.ProductSuite(), + product_suite.ProductSuite(), + ], + next_page_token="abc", + ), + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[], + next_page_token="def", + ), + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[ + product_suite.ProductSuite(), + ], + next_page_token="ghi", + ), + cloud_product_registry_read_service.ListProductSuitesResponse( + product_suites=[ + product_suite.ProductSuite(), + product_suite.ProductSuite(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + cloud_product_registry_read_service.ListProductSuitesResponse.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 = {} + + pager = client.list_product_suites(request=sample_request) + + assert pager.next_page_token == "abc" + assert str(pager).startswith(f"{pager.__class__.__name__}<") + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, product_suite.ProductSuite) for i in results) + + pages = list(client.list_product_suites(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +def test_get_logical_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 = CloudProductRegistryReadServiceClient( + 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_logical_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_logical_product] = ( + mock_rpc + ) + + request = {} + client.get_logical_product(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.get_logical_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_logical_product_rest_required_fields( + request_type=cloud_product_registry_read_service.GetLogicalProductRequest, +): + transport_class = transports.CloudProductRegistryReadServiceRestTransport + + 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_logical_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_logical_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 = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = logical_product.LogicalProduct() + # 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 = logical_product.LogicalProduct.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_logical_product(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert sorted(expected_params) == sorted(actual_params) + + +def test_get_logical_product_rest_unset_required_fields(): + transport = transports.CloudProductRegistryReadServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_logical_product._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +def test_get_logical_product_rest_flattened(): + client = CloudProductRegistryReadServiceClient( + 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 = logical_product.LogicalProduct() + + # get arguments that satisfy an http rule for this method + sample_request = {"name": "logicalProducts/sample1"} + + # 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 = logical_product.LogicalProduct.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_logical_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/v1/{name=logicalProducts/*}" % client.transport._host, args[1] + ) + + +def test_get_logical_product_rest_flattened_error(transport: str = "rest"): + client = CloudProductRegistryReadServiceClient( + 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_logical_product( + cloud_product_registry_read_service.GetLogicalProductRequest(), + name="name_value", + ) + + +def test_list_logical_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 = CloudProductRegistryReadServiceClient( + 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_logical_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_logical_products] = ( + mock_rpc + ) + + request = {} + client.list_logical_products(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.list_logical_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_logical_products_rest_pager(transport: str = "rest"): + client = CloudProductRegistryReadServiceClient( + 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 = ( + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[ + logical_product.LogicalProduct(), + logical_product.LogicalProduct(), + logical_product.LogicalProduct(), + ], + next_page_token="abc", + ), + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[], + next_page_token="def", + ), + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[ + logical_product.LogicalProduct(), + ], + next_page_token="ghi", + ), + cloud_product_registry_read_service.ListLogicalProductsResponse( + logical_products=[ + logical_product.LogicalProduct(), + logical_product.LogicalProduct(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + cloud_product_registry_read_service.ListLogicalProductsResponse.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 = {} + + pager = client.list_logical_products(request=sample_request) + + assert pager.next_page_token == "abc" + assert str(pager).startswith(f"{pager.__class__.__name__}<") + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, logical_product.LogicalProduct) for i in results) + + pages = list(client.list_logical_products(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +def test_get_logical_product_variant_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 = CloudProductRegistryReadServiceClient( + 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_logical_product_variant + 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_logical_product_variant + ] = mock_rpc + + request = {} + client.get_logical_product_variant(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.get_logical_product_variant(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_logical_product_variant_rest_required_fields( + request_type=cloud_product_registry_read_service.GetLogicalProductVariantRequest, +): + transport_class = transports.CloudProductRegistryReadServiceRestTransport + + 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_logical_product_variant._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_logical_product_variant._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 = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = logical_product_variant.LogicalProductVariant() + # 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 = logical_product_variant.LogicalProductVariant.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_logical_product_variant(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert sorted(expected_params) == sorted(actual_params) + + +def test_get_logical_product_variant_rest_unset_required_fields(): + transport = transports.CloudProductRegistryReadServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_logical_product_variant._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +def test_get_logical_product_variant_rest_flattened(): + client = CloudProductRegistryReadServiceClient( + 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 = logical_product_variant.LogicalProductVariant() + + # get arguments that satisfy an http rule for this method + sample_request = {"name": "logicalProducts/sample1/variants/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 = logical_product_variant.LogicalProductVariant.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_logical_product_variant(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v1/{name=logicalProducts/*/variants/*}" % client.transport._host, + args[1], + ) + + +def test_get_logical_product_variant_rest_flattened_error(transport: str = "rest"): + client = CloudProductRegistryReadServiceClient( + 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_logical_product_variant( + cloud_product_registry_read_service.GetLogicalProductVariantRequest(), + name="name_value", + ) + + +def test_list_logical_product_variants_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 = CloudProductRegistryReadServiceClient( + 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_logical_product_variants + 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_logical_product_variants + ] = mock_rpc + + request = {} + client.list_logical_product_variants(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.list_logical_product_variants(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_logical_product_variants_rest_required_fields( + request_type=cloud_product_registry_read_service.ListLogicalProductVariantsRequest, +): + transport_class = transports.CloudProductRegistryReadServiceRestTransport + + 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_logical_product_variants._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_logical_product_variants._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 = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = ( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse() + ) + # 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 = cloud_product_registry_read_service.ListLogicalProductVariantsResponse.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_logical_product_variants(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert sorted(expected_params) == sorted(actual_params) + + +def test_list_logical_product_variants_rest_unset_required_fields(): + transport = transports.CloudProductRegistryReadServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_logical_product_variants._get_unset_required_fields( + {} + ) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("parent",)) + ) + + +def test_list_logical_product_variants_rest_flattened(): + client = CloudProductRegistryReadServiceClient( + 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 = ( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse() + ) + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "logicalProducts/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 = ( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse.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_logical_product_variants(**mock_args) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(req.mock_calls) == 1 + _, args, _ = req.mock_calls[0] + assert path_template.validate( + "%s/v1/{parent=logicalProducts/*}/variants" % client.transport._host, + args[1], + ) + + +def test_list_logical_product_variants_rest_flattened_error(transport: str = "rest"): + client = CloudProductRegistryReadServiceClient( + 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_logical_product_variants( + cloud_product_registry_read_service.ListLogicalProductVariantsRequest(), + parent="parent_value", + ) + + +def test_list_logical_product_variants_rest_pager(transport: str = "rest"): + client = CloudProductRegistryReadServiceClient( + 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 = ( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[ + logical_product_variant.LogicalProductVariant(), + logical_product_variant.LogicalProductVariant(), + logical_product_variant.LogicalProductVariant(), + ], + next_page_token="abc", + ), + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[], + next_page_token="def", + ), + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[ + logical_product_variant.LogicalProductVariant(), + ], + next_page_token="ghi", + ), + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + logical_product_variants=[ + logical_product_variant.LogicalProductVariant(), + logical_product_variant.LogicalProductVariant(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse.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": "logicalProducts/sample1"} + + pager = client.list_logical_product_variants(request=sample_request) + + assert pager.next_page_token == "abc" + assert str(pager).startswith(f"{pager.__class__.__name__}<") + + results = list(pager) + assert len(results) == 6 + assert all( + isinstance(i, logical_product_variant.LogicalProductVariant) + for i in results + ) + + pages = list(client.list_logical_product_variants(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +def test_lookup_entity_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 = CloudProductRegistryReadServiceClient( + 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.lookup_entity 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.lookup_entity] = mock_rpc + + request = {} + client.lookup_entity(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.lookup_entity(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_lookup_entity_rest_required_fields( + request_type=cloud_product_registry_read_service.LookupEntityRequest, +): + transport_class = transports.CloudProductRegistryReadServiceRestTransport + + request_init = {} + request_init["lookup_uri"] = "" + 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() + ).lookup_entity._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["lookupUri"] = "lookup_uri_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).lookup_entity._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "lookupUri" in jsonified_request + assert jsonified_request["lookupUri"] == "lookup_uri_value" + + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = cloud_product_registry_read_service.LookupEntityResponse() + # 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 = cloud_product_registry_read_service.LookupEntityResponse.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.lookup_entity(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert sorted(expected_params) == sorted(actual_params) + + +def test_lookup_entity_rest_unset_required_fields(): + transport = transports.CloudProductRegistryReadServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.lookup_entity._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("lookupUri",))) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.CloudProductRegistryReadServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.CloudProductRegistryReadServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = CloudProductRegistryReadServiceClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.CloudProductRegistryReadServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = CloudProductRegistryReadServiceClient( + 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 = CloudProductRegistryReadServiceClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.CloudProductRegistryReadServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = CloudProductRegistryReadServiceClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.CloudProductRegistryReadServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = CloudProductRegistryReadServiceClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.CloudProductRegistryReadServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.CloudProductRegistryReadServiceGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.CloudProductRegistryReadServiceGrpcTransport, + transports.CloudProductRegistryReadServiceGrpcAsyncIOTransport, + transports.CloudProductRegistryReadServiceRestTransport, + ], +) +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 = CloudProductRegistryReadServiceClient.get_transport_class("grpc")( + credentials=ga_credentials.AnonymousCredentials() + ) + assert transport.kind == "grpc" + + +def test_initialize_client_w_grpc(): + client = CloudProductRegistryReadServiceClient( + 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_suite_empty_call_grpc(): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.get_product_suite), "__call__" + ) as call: + call.return_value = product_suite.ProductSuite() + client.get_product_suite(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.GetProductSuiteRequest() + 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_product_suites_empty_call_grpc(): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.list_product_suites), "__call__" + ) as call: + call.return_value = ( + cloud_product_registry_read_service.ListProductSuitesResponse() + ) + client.list_product_suites(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.ListProductSuitesRequest() + 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_get_logical_product_empty_call_grpc(): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.get_logical_product), "__call__" + ) as call: + call.return_value = logical_product.LogicalProduct() + client.get_logical_product(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.GetLogicalProductRequest() + 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_logical_products_empty_call_grpc(): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.list_logical_products), "__call__" + ) as call: + call.return_value = ( + cloud_product_registry_read_service.ListLogicalProductsResponse() + ) + client.list_logical_products(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.ListLogicalProductsRequest() + 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_get_logical_product_variant_empty_call_grpc(): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.get_logical_product_variant), "__call__" + ) as call: + call.return_value = logical_product_variant.LogicalProductVariant() + client.get_logical_product_variant(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = ( + cloud_product_registry_read_service.GetLogicalProductVariantRequest() + ) + 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_logical_product_variants_empty_call_grpc(): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.list_logical_product_variants), "__call__" + ) as call: + call.return_value = ( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse() + ) + client.list_logical_product_variants(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = ( + cloud_product_registry_read_service.ListLogicalProductVariantsRequest() + ) + 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_lookup_entity_empty_call_grpc(): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object(type(client.transport.lookup_entity), "__call__") as call: + call.return_value = cloud_product_registry_read_service.LookupEntityResponse() + client.lookup_entity(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.LookupEntityRequest() + assert args[0] == request_msg + + +def test_transport_kind_grpc_asyncio(): + transport = CloudProductRegistryReadServiceAsyncClient.get_transport_class( + "grpc_asyncio" + )(credentials=async_anonymous_credentials()) + assert transport.kind == "grpc_asyncio" + + +def test_initialize_client_w_grpc_asyncio(): + client = CloudProductRegistryReadServiceAsyncClient( + 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_suite_empty_call_grpc_asyncio(): + client = CloudProductRegistryReadServiceAsyncClient( + credentials=async_anonymous_credentials(), + transport="grpc_asyncio", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.get_product_suite), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + product_suite.ProductSuite( + name="name_value", + title="title_value", + logical_products=["logical_products_value"], + replaced=True, + replacement="replacement_value", + ) + ) + await client.get_product_suite(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.GetProductSuiteRequest() + 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_product_suites_empty_call_grpc_asyncio(): + client = CloudProductRegistryReadServiceAsyncClient( + credentials=async_anonymous_credentials(), + transport="grpc_asyncio", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.list_product_suites), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + cloud_product_registry_read_service.ListProductSuitesResponse( + next_page_token="next_page_token_value", + ) + ) + await client.list_product_suites(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.ListProductSuitesRequest() + 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_get_logical_product_empty_call_grpc_asyncio(): + client = CloudProductRegistryReadServiceAsyncClient( + credentials=async_anonymous_credentials(), + transport="grpc_asyncio", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.get_logical_product), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + logical_product.LogicalProduct( + name="name_value", + title="title_value", + product_suite="product_suite_value", + variants=["variants_value"], + lifecycle_state=lifecycle_state.LifecycleState.LIFECYCLE_STATE_PUBLIC_PREVIEW, + replaced=True, + replacement="replacement_value", + ) + ) + await client.get_logical_product(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.GetLogicalProductRequest() + 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_logical_products_empty_call_grpc_asyncio(): + client = CloudProductRegistryReadServiceAsyncClient( + credentials=async_anonymous_credentials(), + transport="grpc_asyncio", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.list_logical_products), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + cloud_product_registry_read_service.ListLogicalProductsResponse( + next_page_token="next_page_token_value", + ) + ) + await client.list_logical_products(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.ListLogicalProductsRequest() + 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_get_logical_product_variant_empty_call_grpc_asyncio(): + client = CloudProductRegistryReadServiceAsyncClient( + credentials=async_anonymous_credentials(), + transport="grpc_asyncio", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.get_logical_product_variant), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + logical_product_variant.LogicalProductVariant( + name="name_value", + title="title_value", + lifecycle_state=lifecycle_state.LifecycleState.LIFECYCLE_STATE_PUBLIC_PREVIEW, + replaced=True, + replacement="replacement_value", + ) + ) + await client.get_logical_product_variant(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = ( + cloud_product_registry_read_service.GetLogicalProductVariantRequest() + ) + 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_logical_product_variants_empty_call_grpc_asyncio(): + client = CloudProductRegistryReadServiceAsyncClient( + credentials=async_anonymous_credentials(), + transport="grpc_asyncio", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.list_logical_product_variants), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + next_page_token="next_page_token_value", + ) + ) + await client.list_logical_product_variants(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = ( + cloud_product_registry_read_service.ListLogicalProductVariantsRequest() + ) + 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_lookup_entity_empty_call_grpc_asyncio(): + client = CloudProductRegistryReadServiceAsyncClient( + credentials=async_anonymous_credentials(), + transport="grpc_asyncio", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object(type(client.transport.lookup_entity), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + cloud_product_registry_read_service.LookupEntityResponse() + ) + await client.lookup_entity(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.LookupEntityRequest() + assert args[0] == request_msg + + +def test_transport_kind_rest(): + transport = CloudProductRegistryReadServiceClient.get_transport_class("rest")( + credentials=ga_credentials.AnonymousCredentials() + ) + assert transport.kind == "rest" + + +def test_get_product_suite_rest_bad_request( + request_type=cloud_product_registry_read_service.GetProductSuiteRequest, +): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + # send a request that will satisfy transcoding + request_init = {"name": "productSuites/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.get_product_suite(request) + + +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.GetProductSuiteRequest, + dict, + ], +) +def test_get_product_suite_rest_call_success(request_type): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + # send a request that will satisfy transcoding + request_init = {"name": "productSuites/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 = product_suite.ProductSuite( + name="name_value", + title="title_value", + logical_products=["logical_products_value"], + replaced=True, + replacement="replacement_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 = product_suite.ProductSuite.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_suite(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, product_suite.ProductSuite) + assert response.name == "name_value" + assert response.title == "title_value" + assert response.logical_products == ["logical_products_value"] + assert response.replaced is True + assert response.replacement == "replacement_value" + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_product_suite_rest_interceptors(null_interceptor): + transport = transports.CloudProductRegistryReadServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.CloudProductRegistryReadServiceRestInterceptor(), + ) + client = CloudProductRegistryReadServiceClient(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.CloudProductRegistryReadServiceRestInterceptor, + "post_get_product_suite", + ) as post, + mock.patch.object( + transports.CloudProductRegistryReadServiceRestInterceptor, + "post_get_product_suite_with_metadata", + ) as post_with_metadata, + mock.patch.object( + transports.CloudProductRegistryReadServiceRestInterceptor, + "pre_get_product_suite", + ) as pre, + ): + pre.assert_not_called() + post.assert_not_called() + post_with_metadata.assert_not_called() + pb_message = cloud_product_registry_read_service.GetProductSuiteRequest.pb( + cloud_product_registry_read_service.GetProductSuiteRequest() + ) + 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 = product_suite.ProductSuite.to_json(product_suite.ProductSuite()) + req.return_value.content = return_value + + request = cloud_product_registry_read_service.GetProductSuiteRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = product_suite.ProductSuite() + post_with_metadata.return_value = product_suite.ProductSuite(), metadata + + client.get_product_suite( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + post_with_metadata.assert_called_once() + + +def test_list_product_suites_rest_bad_request( + request_type=cloud_product_registry_read_service.ListProductSuitesRequest, +): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + # send a request that will satisfy transcoding + request_init = {} + 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_product_suites(request) + + +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.ListProductSuitesRequest, + dict, + ], +) +def test_list_product_suites_rest_call_success(request_type): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + # send a request that will satisfy transcoding + request_init = {} + 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 = cloud_product_registry_read_service.ListProductSuitesResponse( + 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 = cloud_product_registry_read_service.ListProductSuitesResponse.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_product_suites(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListProductSuitesPager) + assert response.next_page_token == "next_page_token_value" + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_product_suites_rest_interceptors(null_interceptor): + transport = transports.CloudProductRegistryReadServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.CloudProductRegistryReadServiceRestInterceptor(), + ) + client = CloudProductRegistryReadServiceClient(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.CloudProductRegistryReadServiceRestInterceptor, + "post_list_product_suites", + ) as post, + mock.patch.object( + transports.CloudProductRegistryReadServiceRestInterceptor, + "post_list_product_suites_with_metadata", + ) as post_with_metadata, + mock.patch.object( + transports.CloudProductRegistryReadServiceRestInterceptor, + "pre_list_product_suites", + ) as pre, + ): + pre.assert_not_called() + post.assert_not_called() + post_with_metadata.assert_not_called() + pb_message = cloud_product_registry_read_service.ListProductSuitesRequest.pb( + cloud_product_registry_read_service.ListProductSuitesRequest() + ) + 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 = ( + cloud_product_registry_read_service.ListProductSuitesResponse.to_json( + cloud_product_registry_read_service.ListProductSuitesResponse() + ) + ) + req.return_value.content = return_value + + request = cloud_product_registry_read_service.ListProductSuitesRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = ( + cloud_product_registry_read_service.ListProductSuitesResponse() + ) + post_with_metadata.return_value = ( + cloud_product_registry_read_service.ListProductSuitesResponse(), + metadata, + ) + + client.list_product_suites( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + post_with_metadata.assert_called_once() + + +def test_get_logical_product_rest_bad_request( + request_type=cloud_product_registry_read_service.GetLogicalProductRequest, +): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + # send a request that will satisfy transcoding + request_init = {"name": "logicalProducts/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.get_logical_product(request) + + +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.GetLogicalProductRequest, + dict, + ], +) +def test_get_logical_product_rest_call_success(request_type): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + # send a request that will satisfy transcoding + request_init = {"name": "logicalProducts/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 = logical_product.LogicalProduct( + name="name_value", + title="title_value", + product_suite="product_suite_value", + variants=["variants_value"], + lifecycle_state=lifecycle_state.LifecycleState.LIFECYCLE_STATE_PUBLIC_PREVIEW, + replaced=True, + replacement="replacement_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 = logical_product.LogicalProduct.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_logical_product(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, logical_product.LogicalProduct) + assert response.name == "name_value" + assert response.title == "title_value" + assert response.product_suite == "product_suite_value" + assert response.variants == ["variants_value"] + assert ( + response.lifecycle_state + == lifecycle_state.LifecycleState.LIFECYCLE_STATE_PUBLIC_PREVIEW + ) + assert response.replaced is True + assert response.replacement == "replacement_value" + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_logical_product_rest_interceptors(null_interceptor): + transport = transports.CloudProductRegistryReadServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.CloudProductRegistryReadServiceRestInterceptor(), + ) + client = CloudProductRegistryReadServiceClient(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.CloudProductRegistryReadServiceRestInterceptor, + "post_get_logical_product", + ) as post, + mock.patch.object( + transports.CloudProductRegistryReadServiceRestInterceptor, + "post_get_logical_product_with_metadata", + ) as post_with_metadata, + mock.patch.object( + transports.CloudProductRegistryReadServiceRestInterceptor, + "pre_get_logical_product", + ) as pre, + ): + pre.assert_not_called() + post.assert_not_called() + post_with_metadata.assert_not_called() + pb_message = cloud_product_registry_read_service.GetLogicalProductRequest.pb( + cloud_product_registry_read_service.GetLogicalProductRequest() + ) + 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 = logical_product.LogicalProduct.to_json( + logical_product.LogicalProduct() + ) + req.return_value.content = return_value + + request = cloud_product_registry_read_service.GetLogicalProductRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = logical_product.LogicalProduct() + post_with_metadata.return_value = logical_product.LogicalProduct(), metadata + + client.get_logical_product( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + post_with_metadata.assert_called_once() + + +def test_list_logical_products_rest_bad_request( + request_type=cloud_product_registry_read_service.ListLogicalProductsRequest, +): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + # send a request that will satisfy transcoding + request_init = {} + 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_logical_products(request) + + +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.ListLogicalProductsRequest, + dict, + ], +) +def test_list_logical_products_rest_call_success(request_type): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + # send a request that will satisfy transcoding + request_init = {} + 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 = cloud_product_registry_read_service.ListLogicalProductsResponse( + 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 = ( + cloud_product_registry_read_service.ListLogicalProductsResponse.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_logical_products(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListLogicalProductsPager) + assert response.next_page_token == "next_page_token_value" + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_logical_products_rest_interceptors(null_interceptor): + transport = transports.CloudProductRegistryReadServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.CloudProductRegistryReadServiceRestInterceptor(), + ) + client = CloudProductRegistryReadServiceClient(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.CloudProductRegistryReadServiceRestInterceptor, + "post_list_logical_products", + ) as post, + mock.patch.object( + transports.CloudProductRegistryReadServiceRestInterceptor, + "post_list_logical_products_with_metadata", + ) as post_with_metadata, + mock.patch.object( + transports.CloudProductRegistryReadServiceRestInterceptor, + "pre_list_logical_products", + ) as pre, + ): + pre.assert_not_called() + post.assert_not_called() + post_with_metadata.assert_not_called() + pb_message = cloud_product_registry_read_service.ListLogicalProductsRequest.pb( + cloud_product_registry_read_service.ListLogicalProductsRequest() + ) + 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 = ( + cloud_product_registry_read_service.ListLogicalProductsResponse.to_json( + cloud_product_registry_read_service.ListLogicalProductsResponse() + ) + ) + req.return_value.content = return_value + + request = cloud_product_registry_read_service.ListLogicalProductsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = ( + cloud_product_registry_read_service.ListLogicalProductsResponse() + ) + post_with_metadata.return_value = ( + cloud_product_registry_read_service.ListLogicalProductsResponse(), + metadata, + ) + + client.list_logical_products( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + post_with_metadata.assert_called_once() + + +def test_get_logical_product_variant_rest_bad_request( + request_type=cloud_product_registry_read_service.GetLogicalProductVariantRequest, +): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + # send a request that will satisfy transcoding + request_init = {"name": "logicalProducts/sample1/variants/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_logical_product_variant(request) + + +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.GetLogicalProductVariantRequest, + dict, + ], +) +def test_get_logical_product_variant_rest_call_success(request_type): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + # send a request that will satisfy transcoding + request_init = {"name": "logicalProducts/sample1/variants/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 = logical_product_variant.LogicalProductVariant( + name="name_value", + title="title_value", + lifecycle_state=lifecycle_state.LifecycleState.LIFECYCLE_STATE_PUBLIC_PREVIEW, + replaced=True, + replacement="replacement_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 = logical_product_variant.LogicalProductVariant.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_logical_product_variant(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, logical_product_variant.LogicalProductVariant) + assert response.name == "name_value" + assert response.title == "title_value" + assert ( + response.lifecycle_state + == lifecycle_state.LifecycleState.LIFECYCLE_STATE_PUBLIC_PREVIEW + ) + assert response.replaced is True + assert response.replacement == "replacement_value" + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_logical_product_variant_rest_interceptors(null_interceptor): + transport = transports.CloudProductRegistryReadServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.CloudProductRegistryReadServiceRestInterceptor(), + ) + client = CloudProductRegistryReadServiceClient(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.CloudProductRegistryReadServiceRestInterceptor, + "post_get_logical_product_variant", + ) as post, + mock.patch.object( + transports.CloudProductRegistryReadServiceRestInterceptor, + "post_get_logical_product_variant_with_metadata", + ) as post_with_metadata, + mock.patch.object( + transports.CloudProductRegistryReadServiceRestInterceptor, + "pre_get_logical_product_variant", + ) as pre, + ): + pre.assert_not_called() + post.assert_not_called() + post_with_metadata.assert_not_called() + pb_message = ( + cloud_product_registry_read_service.GetLogicalProductVariantRequest.pb( + cloud_product_registry_read_service.GetLogicalProductVariantRequest() + ) + ) + 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 = logical_product_variant.LogicalProductVariant.to_json( + logical_product_variant.LogicalProductVariant() + ) + req.return_value.content = return_value + + request = cloud_product_registry_read_service.GetLogicalProductVariantRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = logical_product_variant.LogicalProductVariant() + post_with_metadata.return_value = ( + logical_product_variant.LogicalProductVariant(), + metadata, + ) + + client.get_logical_product_variant( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + post_with_metadata.assert_called_once() + + +def test_list_logical_product_variants_rest_bad_request( + request_type=cloud_product_registry_read_service.ListLogicalProductVariantsRequest, +): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + # send a request that will satisfy transcoding + request_init = {"parent": "logicalProducts/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_logical_product_variants(request) + + +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.ListLogicalProductVariantsRequest, + dict, + ], +) +def test_list_logical_product_variants_rest_call_success(request_type): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "logicalProducts/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 = ( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse( + 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 = ( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse.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_logical_product_variants(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListLogicalProductVariantsPager) + assert response.next_page_token == "next_page_token_value" + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_logical_product_variants_rest_interceptors(null_interceptor): + transport = transports.CloudProductRegistryReadServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.CloudProductRegistryReadServiceRestInterceptor(), + ) + client = CloudProductRegistryReadServiceClient(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.CloudProductRegistryReadServiceRestInterceptor, + "post_list_logical_product_variants", + ) as post, + mock.patch.object( + transports.CloudProductRegistryReadServiceRestInterceptor, + "post_list_logical_product_variants_with_metadata", + ) as post_with_metadata, + mock.patch.object( + transports.CloudProductRegistryReadServiceRestInterceptor, + "pre_list_logical_product_variants", + ) as pre, + ): + pre.assert_not_called() + post.assert_not_called() + post_with_metadata.assert_not_called() + pb_message = ( + cloud_product_registry_read_service.ListLogicalProductVariantsRequest.pb( + cloud_product_registry_read_service.ListLogicalProductVariantsRequest() + ) + ) + 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 = cloud_product_registry_read_service.ListLogicalProductVariantsResponse.to_json( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse() + ) + req.return_value.content = return_value + + request = ( + cloud_product_registry_read_service.ListLogicalProductVariantsRequest() + ) + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = ( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse() + ) + post_with_metadata.return_value = ( + cloud_product_registry_read_service.ListLogicalProductVariantsResponse(), + metadata, + ) + + client.list_logical_product_variants( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + post_with_metadata.assert_called_once() + + +def test_lookup_entity_rest_bad_request( + request_type=cloud_product_registry_read_service.LookupEntityRequest, +): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + # send a request that will satisfy transcoding + request_init = {"lookup_uri": "logicalProducts/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.lookup_entity(request) + + +@pytest.mark.parametrize( + "request_type", + [ + cloud_product_registry_read_service.LookupEntityRequest, + dict, + ], +) +def test_lookup_entity_rest_call_success(request_type): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + # send a request that will satisfy transcoding + request_init = {"lookup_uri": "logicalProducts/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 = cloud_product_registry_read_service.LookupEntityResponse() + + # 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 = cloud_product_registry_read_service.LookupEntityResponse.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.lookup_entity(request) + + # Establish that the response is the type that we expect. + assert isinstance( + response, cloud_product_registry_read_service.LookupEntityResponse + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_lookup_entity_rest_interceptors(null_interceptor): + transport = transports.CloudProductRegistryReadServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.CloudProductRegistryReadServiceRestInterceptor(), + ) + client = CloudProductRegistryReadServiceClient(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.CloudProductRegistryReadServiceRestInterceptor, + "post_lookup_entity", + ) as post, + mock.patch.object( + transports.CloudProductRegistryReadServiceRestInterceptor, + "post_lookup_entity_with_metadata", + ) as post_with_metadata, + mock.patch.object( + transports.CloudProductRegistryReadServiceRestInterceptor, + "pre_lookup_entity", + ) as pre, + ): + pre.assert_not_called() + post.assert_not_called() + post_with_metadata.assert_not_called() + pb_message = cloud_product_registry_read_service.LookupEntityRequest.pb( + cloud_product_registry_read_service.LookupEntityRequest() + ) + 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 = cloud_product_registry_read_service.LookupEntityResponse.to_json( + cloud_product_registry_read_service.LookupEntityResponse() + ) + req.return_value.content = return_value + + request = cloud_product_registry_read_service.LookupEntityRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = cloud_product_registry_read_service.LookupEntityResponse() + post_with_metadata.return_value = ( + cloud_product_registry_read_service.LookupEntityResponse(), + metadata, + ) + + client.lookup_entity( + 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 = CloudProductRegistryReadServiceClient( + 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_suite_empty_call_rest(): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.get_product_suite), "__call__" + ) as call: + client.get_product_suite(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.GetProductSuiteRequest() + 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_product_suites_empty_call_rest(): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.list_product_suites), "__call__" + ) as call: + client.list_product_suites(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.ListProductSuitesRequest() + 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_get_logical_product_empty_call_rest(): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.get_logical_product), "__call__" + ) as call: + client.get_logical_product(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.GetLogicalProductRequest() + 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_logical_products_empty_call_rest(): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.list_logical_products), "__call__" + ) as call: + client.list_logical_products(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.ListLogicalProductsRequest() + 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_get_logical_product_variant_empty_call_rest(): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.get_logical_product_variant), "__call__" + ) as call: + client.get_logical_product_variant(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = ( + cloud_product_registry_read_service.GetLogicalProductVariantRequest() + ) + 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_logical_product_variants_empty_call_rest(): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.list_logical_product_variants), "__call__" + ) as call: + client.list_logical_product_variants(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = ( + cloud_product_registry_read_service.ListLogicalProductVariantsRequest() + ) + 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_lookup_entity_empty_call_rest(): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object(type(client.transport.lookup_entity), "__call__") as call: + client.lookup_entity(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = cloud_product_registry_read_service.LookupEntityRequest() + assert args[0] == request_msg + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.CloudProductRegistryReadServiceGrpcTransport, + ) + + +def test_cloud_product_registry_read_service_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.CloudProductRegistryReadServiceTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_cloud_product_registry_read_service_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.productregistry_v1.services.cloud_product_registry_read_service.transports.CloudProductRegistryReadServiceTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.CloudProductRegistryReadServiceTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "get_product_suite", + "list_product_suites", + "get_logical_product", + "list_logical_products", + "get_logical_product_variant", + "list_logical_product_variants", + "lookup_entity", + ) + 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_cloud_product_registry_read_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.cloud.productregistry_v1.services.cloud_product_registry_read_service.transports.CloudProductRegistryReadServiceTransport._prep_wrapped_messages" + ) as Transport, + ): + Transport.return_value = None + load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.CloudProductRegistryReadServiceTransport( + credentials_file="credentials.json", + quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=None, + default_scopes=(), + quota_project_id="octopus", + ) + + +def test_cloud_product_registry_read_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.cloud.productregistry_v1.services.cloud_product_registry_read_service.transports.CloudProductRegistryReadServiceTransport._prep_wrapped_messages" + ) as Transport, + ): + Transport.return_value = None + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.CloudProductRegistryReadServiceTransport() + adc.assert_called_once() + + +def test_cloud_product_registry_read_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) + CloudProductRegistryReadServiceClient() + adc.assert_called_once_with( + scopes=None, + default_scopes=(), + quota_project_id=None, + ) + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.CloudProductRegistryReadServiceGrpcTransport, + transports.CloudProductRegistryReadServiceGrpcAsyncIOTransport, + ], +) +def test_cloud_product_registry_read_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=(), + quota_project_id="octopus", + ) + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.CloudProductRegistryReadServiceGrpcTransport, + transports.CloudProductRegistryReadServiceGrpcAsyncIOTransport, + transports.CloudProductRegistryReadServiceRestTransport, + ], +) +def test_cloud_product_registry_read_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.CloudProductRegistryReadServiceGrpcTransport, grpc_helpers), + ( + transports.CloudProductRegistryReadServiceGrpcAsyncIOTransport, + grpc_helpers_async, + ), + ], +) +def test_cloud_product_registry_read_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( + "cloudproductregistry.googleapis.com:443", + credentials=creds, + credentials_file=None, + quota_project_id="octopus", + default_scopes=(), + scopes=["1", "2"], + default_host="cloudproductregistry.googleapis.com", + ssl_credentials=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.CloudProductRegistryReadServiceGrpcTransport, + transports.CloudProductRegistryReadServiceGrpcAsyncIOTransport, + ], +) +def test_cloud_product_registry_read_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_cloud_product_registry_read_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.CloudProductRegistryReadServiceRestTransport( + 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_cloud_product_registry_read_service_host_no_port(transport_name): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions( + api_endpoint="cloudproductregistry.googleapis.com" + ), + transport=transport_name, + ) + assert client.transport._host == ( + "cloudproductregistry.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://cloudproductregistry.googleapis.com" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "grpc_asyncio", + "rest", + ], +) +def test_cloud_product_registry_read_service_host_with_port(transport_name): + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions( + api_endpoint="cloudproductregistry.googleapis.com:8000" + ), + transport=transport_name, + ) + assert client.transport._host == ( + "cloudproductregistry.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://cloudproductregistry.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_cloud_product_registry_read_service_client_transport_session_collision( + transport_name, +): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = CloudProductRegistryReadServiceClient( + credentials=creds1, + transport=transport_name, + ) + client2 = CloudProductRegistryReadServiceClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.get_product_suite._session + session2 = client2.transport.get_product_suite._session + assert session1 != session2 + session1 = client1.transport.list_product_suites._session + session2 = client2.transport.list_product_suites._session + assert session1 != session2 + session1 = client1.transport.get_logical_product._session + session2 = client2.transport.get_logical_product._session + assert session1 != session2 + session1 = client1.transport.list_logical_products._session + session2 = client2.transport.list_logical_products._session + assert session1 != session2 + session1 = client1.transport.get_logical_product_variant._session + session2 = client2.transport.get_logical_product_variant._session + assert session1 != session2 + session1 = client1.transport.list_logical_product_variants._session + session2 = client2.transport.list_logical_product_variants._session + assert session1 != session2 + session1 = client1.transport.lookup_entity._session + session2 = client2.transport.lookup_entity._session + assert session1 != session2 + + +def test_cloud_product_registry_read_service_grpc_transport_channel(): + channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials()) + + # Check that channel is used if provided. + transport = transports.CloudProductRegistryReadServiceGrpcTransport( + 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_cloud_product_registry_read_service_grpc_asyncio_transport_channel(): + channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials()) + + # Check that channel is used if provided. + transport = transports.CloudProductRegistryReadServiceGrpcAsyncIOTransport( + 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.filterwarnings("ignore::FutureWarning") +@pytest.mark.parametrize( + "transport_class", + [ + transports.CloudProductRegistryReadServiceGrpcTransport, + transports.CloudProductRegistryReadServiceGrpcAsyncIOTransport, + ], +) +def test_cloud_product_registry_read_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.CloudProductRegistryReadServiceGrpcTransport, + transports.CloudProductRegistryReadServiceGrpcAsyncIOTransport, + ], +) +def test_cloud_product_registry_read_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_logical_product_path(): + logical_product = "squid" + expected = "logicalProducts/{logical_product}".format( + logical_product=logical_product, + ) + actual = CloudProductRegistryReadServiceClient.logical_product_path(logical_product) + assert expected == actual + + +def test_parse_logical_product_path(): + expected = { + "logical_product": "clam", + } + path = CloudProductRegistryReadServiceClient.logical_product_path(**expected) + + # Check that the path construction is reversible. + actual = CloudProductRegistryReadServiceClient.parse_logical_product_path(path) + assert expected == actual + + +def test_logical_product_variant_path(): + logical_product = "whelk" + variant = "octopus" + expected = "logicalProducts/{logical_product}/variants/{variant}".format( + logical_product=logical_product, + variant=variant, + ) + actual = CloudProductRegistryReadServiceClient.logical_product_variant_path( + logical_product, variant + ) + assert expected == actual + + +def test_parse_logical_product_variant_path(): + expected = { + "logical_product": "oyster", + "variant": "nudibranch", + } + path = CloudProductRegistryReadServiceClient.logical_product_variant_path( + **expected + ) + + # Check that the path construction is reversible. + actual = CloudProductRegistryReadServiceClient.parse_logical_product_variant_path( + path + ) + assert expected == actual + + +def test_product_suite_path(): + product_suite = "cuttlefish" + expected = "productSuites/{product_suite}".format( + product_suite=product_suite, + ) + actual = CloudProductRegistryReadServiceClient.product_suite_path(product_suite) + assert expected == actual + + +def test_parse_product_suite_path(): + expected = { + "product_suite": "mussel", + } + path = CloudProductRegistryReadServiceClient.product_suite_path(**expected) + + # Check that the path construction is reversible. + actual = CloudProductRegistryReadServiceClient.parse_product_suite_path(path) + assert expected == actual + + +def test_common_billing_account_path(): + billing_account = "winkle" + expected = "billingAccounts/{billing_account}".format( + billing_account=billing_account, + ) + actual = CloudProductRegistryReadServiceClient.common_billing_account_path( + billing_account + ) + assert expected == actual + + +def test_parse_common_billing_account_path(): + expected = { + "billing_account": "nautilus", + } + path = CloudProductRegistryReadServiceClient.common_billing_account_path(**expected) + + # Check that the path construction is reversible. + actual = CloudProductRegistryReadServiceClient.parse_common_billing_account_path( + path + ) + assert expected == actual + + +def test_common_folder_path(): + folder = "scallop" + expected = "folders/{folder}".format( + folder=folder, + ) + actual = CloudProductRegistryReadServiceClient.common_folder_path(folder) + assert expected == actual + + +def test_parse_common_folder_path(): + expected = { + "folder": "abalone", + } + path = CloudProductRegistryReadServiceClient.common_folder_path(**expected) + + # Check that the path construction is reversible. + actual = CloudProductRegistryReadServiceClient.parse_common_folder_path(path) + assert expected == actual + + +def test_common_organization_path(): + organization = "squid" + expected = "organizations/{organization}".format( + organization=organization, + ) + actual = CloudProductRegistryReadServiceClient.common_organization_path( + organization + ) + assert expected == actual + + +def test_parse_common_organization_path(): + expected = { + "organization": "clam", + } + path = CloudProductRegistryReadServiceClient.common_organization_path(**expected) + + # Check that the path construction is reversible. + actual = CloudProductRegistryReadServiceClient.parse_common_organization_path(path) + assert expected == actual + + +def test_common_project_path(): + project = "whelk" + expected = "projects/{project}".format( + project=project, + ) + actual = CloudProductRegistryReadServiceClient.common_project_path(project) + assert expected == actual + + +def test_parse_common_project_path(): + expected = { + "project": "octopus", + } + path = CloudProductRegistryReadServiceClient.common_project_path(**expected) + + # Check that the path construction is reversible. + actual = CloudProductRegistryReadServiceClient.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 = CloudProductRegistryReadServiceClient.common_location_path( + project, location + ) + assert expected == actual + + +def test_parse_common_location_path(): + expected = { + "project": "cuttlefish", + "location": "mussel", + } + path = CloudProductRegistryReadServiceClient.common_location_path(**expected) + + # Check that the path construction is reversible. + actual = CloudProductRegistryReadServiceClient.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.CloudProductRegistryReadServiceTransport, "_prep_wrapped_messages" + ) as prep: + client = CloudProductRegistryReadServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + with mock.patch.object( + transports.CloudProductRegistryReadServiceTransport, "_prep_wrapped_messages" + ) as prep: + transport_class = CloudProductRegistryReadServiceClient.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 = CloudProductRegistryReadServiceClient( + 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 = CloudProductRegistryReadServiceAsyncClient( + 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 = CloudProductRegistryReadServiceClient( + 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 = CloudProductRegistryReadServiceClient( + 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", + [ + ( + CloudProductRegistryReadServiceClient, + transports.CloudProductRegistryReadServiceGrpcTransport, + ), + ( + CloudProductRegistryReadServiceAsyncClient, + transports.CloudProductRegistryReadServiceGrpcAsyncIOTransport, + ), + ], +) +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-cloud-pubsub/noxfile.py b/packages/google-cloud-pubsub/noxfile.py index 066ad8cbd524..196349acae88 100644 --- a/packages/google-cloud-pubsub/noxfile.py +++ b/packages/google-cloud-pubsub/noxfile.py @@ -94,6 +94,17 @@ SYSTEM_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = {} CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) + nox.options.sessions = [ "unit", @@ -141,7 +152,7 @@ def mypy(session): # mypy checks yet. # https://github.com/googleapis/gapic-generator-python/issues/1092 # TODO: Re-enable mypy checks once we merge, since incremental checks are failing due to protobuf upgrade - # session.run("mypy", "-p", "google.cloud", "--exclude", "google/pubsub_v1/") + # session.run("mypy", f"--config-file={MYPY_CONFIG_FILE}", "-p", "google.cloud", "--exclude", "google/pubsub_v1/") @nox.session(python=DEFAULT_PYTHON_VERSION) diff --git a/packages/google-cloud-runtimeconfig/noxfile.py b/packages/google-cloud-runtimeconfig/noxfile.py index 42e77572607e..f4e52787ebf7 100644 --- a/packages/google-cloud-runtimeconfig/noxfile.py +++ b/packages/google-cloud-runtimeconfig/noxfile.py @@ -68,6 +68,17 @@ SYSTEM_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = {} CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) + # Error if a python version is missing nox.options.error_on_missing_interpreters = True @@ -100,6 +111,7 @@ def mypy(session): session.install(".") session.run( "mypy", + f"--config-file={MYPY_CONFIG_FILE}", "-p", "google", ) diff --git a/packages/google-cloud-spanner-dbapi-driver/mypy.ini b/packages/google-cloud-spanner-dbapi-driver/mypy.ini deleted file mode 100644 index 81b11c94badc..000000000000 --- a/packages/google-cloud-spanner-dbapi-driver/mypy.ini +++ /dev/null @@ -1,15 +0,0 @@ -[mypy] -python_version = 3.11 -namespace_packages = True -ignore_missing_imports = False - -# TODO(https://github.com/googleapis/gapic-generator-python/issues/2563): -# Dependencies that historically lacks py.typed markers -[mypy-google.iam.*] -ignore_missing_imports = True - -# Helps mypy navigate the 'google' namespace more reliably in 3.10+ -explicit_package_bases = True - -# Performance: reuse results from previous runs to speed up 'nox' -incremental = True diff --git a/packages/google-cloud-spanner-dbapi-driver/noxfile.py b/packages/google-cloud-spanner-dbapi-driver/noxfile.py index 2fedee7ee5af..6eb00ef8a6b6 100644 --- a/packages/google-cloud-spanner-dbapi-driver/noxfile.py +++ b/packages/google-cloud-spanner-dbapi-driver/noxfile.py @@ -53,6 +53,17 @@ PREVIEW_PYTHON_VERSION = "3.14" CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) + LOWER_BOUND_CONSTRAINTS_FILE = CURRENT_DIRECTORY / "constraints.txt" PACKAGE_NAME = "google-cloud-spanner-dbapi-driver" @@ -115,6 +126,7 @@ def mypy(session): session.install(".") session.run( "mypy", + f"--config-file={MYPY_CONFIG_FILE}", "-p", "google", ) diff --git a/packages/google-cloud-spanner/noxfile.py b/packages/google-cloud-spanner/noxfile.py index fa74716b8142..d0a3573f3505 100644 --- a/packages/google-cloud-spanner/noxfile.py +++ b/packages/google-cloud-spanner/noxfile.py @@ -36,6 +36,7 @@ "3.12", "3.13", "3.14", + "3.15", ] UNIT_TEST_STANDARD_DEPENDENCIES = [ "mock", @@ -214,30 +215,16 @@ def install_unittest_dependencies(session, *constraints): @nox.session(python=ALL_PYTHON) @nox.parametrize( "protobuf_implementation", - ["python", "upb", "cpp"], + ["python", "upb"], ) def unit(session, protobuf_implementation): # Install all test dependencies, then install this package in-place. - if protobuf_implementation == "cpp" and session.python in ( - "3.11", - "3.12", - "3.13", - "3.14", - ): - session.skip("cpp implementation is not supported in python 3.11+") - constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" ) install_unittest_dependencies(session, "-c", constraints_path) - # TODO(https://github.com/googleapis/synthtool/issues/1976): - # Remove the 'cpp' implementation once support for Protobuf 3.x is dropped. - # The 'cpp' implementation requires Protobuf<4. - if protobuf_implementation == "cpp": - session.install("protobuf<4") - # Run py.test against the unit tests. args = [ "py.test", @@ -333,8 +320,6 @@ def install_systemtest_dependencies(session, *constraints): ("python", "POSTGRESQL"), ("upb", "GOOGLE_STANDARD_SQL"), ("upb", "POSTGRESQL"), - ("cpp", "GOOGLE_STANDARD_SQL"), - ("cpp", "POSTGRESQL"), ], ) def system(session, protobuf_implementation, database_dialect): @@ -362,14 +347,6 @@ def system(session, protobuf_implementation, database_dialect): "Only run system tests on real Spanner with one protobuf implementation to speed up the build" ) - if protobuf_implementation == "cpp" and session.python in ( - "3.11", - "3.12", - "3.13", - "3.14", - ): - session.skip("cpp implementation is not supported in python 3.11+") - # Install pyopenssl for mTLS testing. if os.environ.get("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true": session.install("pyopenssl") @@ -382,12 +359,6 @@ def system(session, protobuf_implementation, database_dialect): install_systemtest_dependencies(session, "-c", constraints_path) - # TODO(https://github.com/googleapis/synthtool/issues/1976): - # Remove the 'cpp' implementation once support for Protobuf 3.x is dropped. - # The 'cpp' implementation requires Protobuf<4. - if protobuf_implementation == "cpp": - session.install("protobuf<4") - # Run py.test against the system tests. if system_test_exists: args = [ @@ -561,21 +532,11 @@ def docfx(session): ("python", "POSTGRESQL"), ("upb", "GOOGLE_STANDARD_SQL"), ("upb", "POSTGRESQL"), - ("cpp", "GOOGLE_STANDARD_SQL"), - ("cpp", "POSTGRESQL"), ], ) def prerelease_deps(session, protobuf_implementation, database_dialect): """Run all tests with prerelease versions of dependencies installed.""" - if protobuf_implementation == "cpp" and session.python in ( - "3.11", - "3.12", - "3.13", - "3.14", - ): - session.skip("cpp implementation is not supported in python 3.11+") - # Install all dependencies session.install("-e", ".[all, tests, tracing]") unit_deps_all = UNIT_TEST_STANDARD_DEPENDENCIES + UNIT_TEST_EXTERNAL_DEPENDENCIES diff --git a/packages/google-cloud-testutils/mypy.ini b/packages/google-cloud-testutils/mypy.ini deleted file mode 100644 index 569486a9ce60..000000000000 --- a/packages/google-cloud-testutils/mypy.ini +++ /dev/null @@ -1,3 +0,0 @@ -[mypy] -python_version = 3.10 -exclude = tests/unit/resources/ diff --git a/packages/google-cloud-testutils/noxfile.py b/packages/google-cloud-testutils/noxfile.py index 0e86a298190e..caebbf8ef459 100644 --- a/packages/google-cloud-testutils/noxfile.py +++ b/packages/google-cloud-testutils/noxfile.py @@ -39,6 +39,16 @@ RUFF_VERSION = "ruff==0.14.14" BLACK_PATHS = ["test_utils", "setup.py"] CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) @nox.session(python=DEFAULT_PYTHON_VERSION) @@ -80,8 +90,10 @@ def format(session): # 2. Run Ruff to fix imports session.run( - "ruff", "check", - "--select", "I", + "ruff", + "check", + "--select", + "I", "--fix", f"--target-version=py{ALL_PYTHON[0].replace('.', '')}", "--line-length=88", @@ -90,7 +102,8 @@ def format(session): # 3. Run Ruff to format code session.run( - "ruff", "format", + "ruff", + "format", f"--target-version=py{ALL_PYTHON[0].replace('.', '')}", "--line-length=88", *BLACK_PATHS, @@ -113,7 +126,7 @@ def mypy(session): "types-mock", "types-setuptools", ) - session.run("mypy", "test_utils/", "tests/") + session.run("mypy", f"--config-file={MYPY_CONFIG_FILE}", "test_utils/", "tests/") @nox.session(python=ALL_PYTHON) @@ -328,14 +341,12 @@ def prerelease_deps(session): ) - @nox.session(python=DEFAULT_PYTHON_VERSION) def core_deps_from_source(session): """Run all tests with core dependencies installed from source rather than pulling the dependencies from PyPI. """ - # Install all dependencies constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" diff --git a/packages/google-maps-isochrones/.coveragerc b/packages/google-maps-isochrones/.coveragerc new file mode 100644 index 000000000000..eff9261593bb --- /dev/null +++ b/packages/google-maps-isochrones/.coveragerc @@ -0,0 +1,13 @@ +[run] +branch = True + +[report] +show_missing = True +omit = + google/maps/isochrones/__init__.py + google/maps/isochrones/gapic_version.py +exclude_lines = + # Re-enable the standard pragma + pragma: NO COVER + # Ignore debug-only repr + def __repr__ diff --git a/packages/google-maps-isochrones/.flake8 b/packages/google-maps-isochrones/.flake8 new file mode 100644 index 000000000000..f9069a84687b --- /dev/null +++ b/packages/google-maps-isochrones/.flake8 @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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. +# +[flake8] +# TODO(https://github.com/googleapis/gapic-generator-python/issues/2333): +# Resolve flake8 lint issues +ignore = E203, E231, E266, E501, W503 +exclude = + # TODO(https://github.com/googleapis/gapic-generator-python/issues/2333): + # Ensure that generated code passes flake8 lint + **/gapic/** + **/services/** + **/types/** + # Exclude Protobuf gencode + *_pb2.py + + # Standard linting exemptions. + **/.nox/** + __pycache__, + .git, + *.pyc, + conf.py diff --git a/packages/google-maps-isochrones/.repo-metadata.json b/packages/google-maps-isochrones/.repo-metadata.json new file mode 100644 index 000000000000..e6753e73dbb6 --- /dev/null +++ b/packages/google-maps-isochrones/.repo-metadata.json @@ -0,0 +1,16 @@ +{ + "api_description": "isochrones.googleapis.com API.", + "api_id": "isochrones.googleapis.com", + "api_shortname": "isochrones", + "client_documentation": "https://googleapis.dev/python/google-maps-isochrones/latest", + "default_version": "v1", + "distribution_name": "google-maps-isochrones", + "issue_tracker": "https://issuetracker.google.com/issues/new?component=1950715\u0026template=0", + "language": "python", + "library_type": "GAPIC_AUTO", + "name": "google-maps-isochrones", + "name_pretty": "Isochrones", + "product_documentation": "https://developers.google.com/maps/documentation/isochrones/overview", + "release_level": "preview", + "repo": "googleapis/google-cloud-python" +} \ No newline at end of file diff --git a/packages/google-maps-isochrones/CHANGELOG.md b/packages/google-maps-isochrones/CHANGELOG.md new file mode 100644 index 000000000000..b2a149b640c9 --- /dev/null +++ b/packages/google-maps-isochrones/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +## 0.1.0 (2026-07-21) + + +### Features + +* **google/maps/isochrones/v1:** add google-maps-isochrones ([#17785](https://github.com/googleapis/google-cloud-python/issues/17785)) ([47a0db2](https://github.com/googleapis/google-cloud-python/commit/47a0db2d9c11e13b64971ede909722ec20e3cab7)) + +## Changelog + +[PyPI History][1] + +[1]: https://pypi.org/project/google-maps-isochrones/#history diff --git a/packages/google-maps-isochrones/LICENSE b/packages/google-maps-isochrones/LICENSE new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/packages/google-maps-isochrones/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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-maps-isochrones/MANIFEST.in b/packages/google-maps-isochrones/MANIFEST.in new file mode 100644 index 000000000000..f932577add9d --- /dev/null +++ b/packages/google-maps-isochrones/MANIFEST.in @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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. +# +include README.rst LICENSE +recursive-include google *.py *.pyi *.json *.proto py.typed +recursive-include tests * +global-exclude *.py[co] +global-exclude __pycache__ diff --git a/packages/google-maps-isochrones/README.rst b/packages/google-maps-isochrones/README.rst new file mode 100644 index 000000000000..139933de6b5f --- /dev/null +++ b/packages/google-maps-isochrones/README.rst @@ -0,0 +1,198 @@ +Python Client for Isochrones +============================ + +|preview| |pypi| |versions| + +`Isochrones`_: isochrones.googleapis.com API. + +- `Client Library Documentation`_ +- `Product Documentation`_ + +.. |preview| image:: https://img.shields.io/badge/support-preview-orange.svg + :target: https://github.com/googleapis/google-cloud-python/blob/main/README.rst#stability-levels +.. |pypi| image:: https://img.shields.io/pypi/v/google-maps-isochrones.svg + :target: https://pypi.org/project/google-maps-isochrones/ +.. |versions| image:: https://img.shields.io/pypi/pyversions/google-maps-isochrones.svg + :target: https://pypi.org/project/google-maps-isochrones/ +.. _Isochrones: https://developers.google.com/maps/documentation/isochrones/overview +.. _Client Library Documentation: https://googleapis.dev/python/google-maps-isochrones/latest +.. _Product Documentation: https://developers.google.com/maps/documentation/isochrones/overview + +Quick Start +----------- + +In order to use this library, you first need to go through the following steps: + +1. `Select or create a Cloud Platform project.`_ +2. `Enable billing for your project.`_ +3. `Enable the Isochrones.`_ +4. `Set up Authentication.`_ + +.. _Select or create a Cloud Platform project.: https://console.cloud.google.com/project +.. _Enable billing for your project.: https://cloud.google.com/billing/docs/how-to/modify-project#enable_billing_for_a_project +.. _Enable the Isochrones.: https://developers.google.com/maps/documentation/isochrones/overview +.. _Set up Authentication.: https://googleapis.dev/python/google-api-core/latest/auth.html + +Installation +~~~~~~~~~~~~ + +Install this library in a virtual environment using `venv`_. `venv`_ is a tool that +creates isolated Python environments. These isolated environments can have separate +versions of Python packages, which allows you to isolate one project's dependencies +from the dependencies of other projects. + +With `venv`_, it's possible to install this library without needing system +install permissions, and without clashing with the installed system +dependencies. + +.. _`venv`: https://docs.python.org/3/library/venv.html + + +Code samples and snippets +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Code samples and snippets live in the `samples/`_ folder. + +.. _samples/: https://github.com/googleapis/google-cloud-python/tree/main/packages/google-maps-isochrones/samples + + +Supported Python Versions +^^^^^^^^^^^^^^^^^^^^^^^^^ +Our client libraries are compatible with all current `active`_ and `maintenance`_ versions of +Python. + +Python >= 3.10, including 3.14 + +.. _active: https://devguide.python.org/devcycle/#in-development-main-branch +.. _maintenance: https://devguide.python.org/devcycle/#maintenance-branches + +Unsupported Python Versions +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Python <= 3.9 + + +If you are using an `end-of-life`_ +version of Python, we recommend that you update as soon as possible to an actively supported version. + +.. _end-of-life: https://devguide.python.org/devcycle/#end-of-life-branches + +Mac/Linux +^^^^^^^^^ + +.. code-block:: console + + python3 -m venv + source /bin/activate + pip install google-maps-isochrones + + +Windows +^^^^^^^ + +.. code-block:: console + + py -m venv + .\\Scripts\activate + pip install google-maps-isochrones + +Next Steps +~~~~~~~~~~ + +- Read the `Client Library Documentation`_ for Isochrones + to see other available methods on the client. +- Read the `Isochrones Product documentation`_ to learn + more about the product and see How-to Guides. +- View this `README`_ to see the full list of Cloud + APIs that we cover. + +.. _Isochrones Product documentation: https://developers.google.com/maps/documentation/isochrones/overview +.. _README: https://github.com/googleapis/google-cloud-python/blob/main/README.rst + +Logging +------- + +This library uses the standard Python :code:`logging` functionality to log some RPC events that could be of interest for debugging and monitoring purposes. +Note the following: + +#. Logs may contain sensitive information. Take care to **restrict access to the logs** if they are saved, whether it be on local storage or on Google Cloud Logging. +#. Google may refine the occurrence, level, and content of various log messages in this library without flagging such changes as breaking. **Do not depend on immutability of the logging events**. +#. By default, the logging events from this library are not handled. You must **explicitly configure log handling** using one of the mechanisms below. + +Simple, environment-based configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To enable logging for this library without any changes in your code, set the :code:`GOOGLE_SDK_PYTHON_LOGGING_SCOPE` environment variable to a valid Google +logging scope. This configures handling of logging events (at level :code:`logging.DEBUG` or higher) from this library in a default manner, emitting the logged +messages in a structured format. It does not currently allow customizing the logging levels captured nor the handlers, formatters, etc. used for any logging +event. + +A logging scope is a period-separated namespace that begins with :code:`google`, identifying the Python module or package to log. + +- Valid logging scopes: :code:`google`, :code:`google.cloud.asset.v1`, :code:`google.api`, :code:`google.auth`, etc. +- Invalid logging scopes: :code:`foo`, :code:`123`, etc. + +**NOTE**: If the logging scope is invalid, the library does not set up any logging handlers. + +Environment-Based Examples +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Enabling the default handler for all Google-based loggers + +.. code-block:: console + + export GOOGLE_SDK_PYTHON_LOGGING_SCOPE=google + +- Enabling the default handler for a specific Google module (for a client library called :code:`library_v1`): + +.. code-block:: console + + export GOOGLE_SDK_PYTHON_LOGGING_SCOPE=google.cloud.library_v1 + + +Advanced, code-based configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also configure a valid logging scope using Python's standard `logging` mechanism. + +Code-Based Examples +^^^^^^^^^^^^^^^^^^^ + +- Configuring a handler for all Google-based loggers + +.. code-block:: python + + import logging + + from google.cloud import library_v1 + + base_logger = logging.getLogger("google") + base_logger.addHandler(logging.StreamHandler()) + base_logger.setLevel(logging.DEBUG) + +- Configuring a handler for a specific Google module (for a client library called :code:`library_v1`): + +.. code-block:: python + + import logging + + from google.cloud import library_v1 + + base_logger = logging.getLogger("google.cloud.library_v1") + base_logger.addHandler(logging.StreamHandler()) + base_logger.setLevel(logging.DEBUG) + +Logging details +~~~~~~~~~~~~~~~ + +#. Regardless of which of the mechanisms above you use to configure logging for this library, by default logging events are not propagated up to the root + logger from the `google`-level logger. If you need the events to be propagated to the root logger, you must explicitly set + :code:`logging.getLogger("google").propagate = True` in your code. +#. You can mix the different logging configurations above for different Google modules. For example, you may want use a code-based logging configuration for + one library, but decide you need to also set up environment-based logging configuration for another library. + + #. If you attempt to use both code-based and environment-based configuration for the same module, the environment-based configuration will be ineffectual + if the code -based configuration gets applied first. + +#. The Google-specific logging configurations (default handlers for environment-based configuration; not propagating logging events to the root logger) get + executed the first time *any* client library is instantiated in your application, and only if the affected loggers have not been previously configured. + (This is the reason for 2.i. above.) diff --git a/packages/google-maps-isochrones/docs/CHANGELOG.md b/packages/google-maps-isochrones/docs/CHANGELOG.md new file mode 120000 index 000000000000..04c99a55caae --- /dev/null +++ b/packages/google-maps-isochrones/docs/CHANGELOG.md @@ -0,0 +1 @@ +../CHANGELOG.md \ No newline at end of file diff --git a/packages/google-maps-isochrones/docs/README.rst b/packages/google-maps-isochrones/docs/README.rst new file mode 100644 index 000000000000..139933de6b5f --- /dev/null +++ b/packages/google-maps-isochrones/docs/README.rst @@ -0,0 +1,198 @@ +Python Client for Isochrones +============================ + +|preview| |pypi| |versions| + +`Isochrones`_: isochrones.googleapis.com API. + +- `Client Library Documentation`_ +- `Product Documentation`_ + +.. |preview| image:: https://img.shields.io/badge/support-preview-orange.svg + :target: https://github.com/googleapis/google-cloud-python/blob/main/README.rst#stability-levels +.. |pypi| image:: https://img.shields.io/pypi/v/google-maps-isochrones.svg + :target: https://pypi.org/project/google-maps-isochrones/ +.. |versions| image:: https://img.shields.io/pypi/pyversions/google-maps-isochrones.svg + :target: https://pypi.org/project/google-maps-isochrones/ +.. _Isochrones: https://developers.google.com/maps/documentation/isochrones/overview +.. _Client Library Documentation: https://googleapis.dev/python/google-maps-isochrones/latest +.. _Product Documentation: https://developers.google.com/maps/documentation/isochrones/overview + +Quick Start +----------- + +In order to use this library, you first need to go through the following steps: + +1. `Select or create a Cloud Platform project.`_ +2. `Enable billing for your project.`_ +3. `Enable the Isochrones.`_ +4. `Set up Authentication.`_ + +.. _Select or create a Cloud Platform project.: https://console.cloud.google.com/project +.. _Enable billing for your project.: https://cloud.google.com/billing/docs/how-to/modify-project#enable_billing_for_a_project +.. _Enable the Isochrones.: https://developers.google.com/maps/documentation/isochrones/overview +.. _Set up Authentication.: https://googleapis.dev/python/google-api-core/latest/auth.html + +Installation +~~~~~~~~~~~~ + +Install this library in a virtual environment using `venv`_. `venv`_ is a tool that +creates isolated Python environments. These isolated environments can have separate +versions of Python packages, which allows you to isolate one project's dependencies +from the dependencies of other projects. + +With `venv`_, it's possible to install this library without needing system +install permissions, and without clashing with the installed system +dependencies. + +.. _`venv`: https://docs.python.org/3/library/venv.html + + +Code samples and snippets +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Code samples and snippets live in the `samples/`_ folder. + +.. _samples/: https://github.com/googleapis/google-cloud-python/tree/main/packages/google-maps-isochrones/samples + + +Supported Python Versions +^^^^^^^^^^^^^^^^^^^^^^^^^ +Our client libraries are compatible with all current `active`_ and `maintenance`_ versions of +Python. + +Python >= 3.10, including 3.14 + +.. _active: https://devguide.python.org/devcycle/#in-development-main-branch +.. _maintenance: https://devguide.python.org/devcycle/#maintenance-branches + +Unsupported Python Versions +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Python <= 3.9 + + +If you are using an `end-of-life`_ +version of Python, we recommend that you update as soon as possible to an actively supported version. + +.. _end-of-life: https://devguide.python.org/devcycle/#end-of-life-branches + +Mac/Linux +^^^^^^^^^ + +.. code-block:: console + + python3 -m venv + source /bin/activate + pip install google-maps-isochrones + + +Windows +^^^^^^^ + +.. code-block:: console + + py -m venv + .\\Scripts\activate + pip install google-maps-isochrones + +Next Steps +~~~~~~~~~~ + +- Read the `Client Library Documentation`_ for Isochrones + to see other available methods on the client. +- Read the `Isochrones Product documentation`_ to learn + more about the product and see How-to Guides. +- View this `README`_ to see the full list of Cloud + APIs that we cover. + +.. _Isochrones Product documentation: https://developers.google.com/maps/documentation/isochrones/overview +.. _README: https://github.com/googleapis/google-cloud-python/blob/main/README.rst + +Logging +------- + +This library uses the standard Python :code:`logging` functionality to log some RPC events that could be of interest for debugging and monitoring purposes. +Note the following: + +#. Logs may contain sensitive information. Take care to **restrict access to the logs** if they are saved, whether it be on local storage or on Google Cloud Logging. +#. Google may refine the occurrence, level, and content of various log messages in this library without flagging such changes as breaking. **Do not depend on immutability of the logging events**. +#. By default, the logging events from this library are not handled. You must **explicitly configure log handling** using one of the mechanisms below. + +Simple, environment-based configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To enable logging for this library without any changes in your code, set the :code:`GOOGLE_SDK_PYTHON_LOGGING_SCOPE` environment variable to a valid Google +logging scope. This configures handling of logging events (at level :code:`logging.DEBUG` or higher) from this library in a default manner, emitting the logged +messages in a structured format. It does not currently allow customizing the logging levels captured nor the handlers, formatters, etc. used for any logging +event. + +A logging scope is a period-separated namespace that begins with :code:`google`, identifying the Python module or package to log. + +- Valid logging scopes: :code:`google`, :code:`google.cloud.asset.v1`, :code:`google.api`, :code:`google.auth`, etc. +- Invalid logging scopes: :code:`foo`, :code:`123`, etc. + +**NOTE**: If the logging scope is invalid, the library does not set up any logging handlers. + +Environment-Based Examples +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Enabling the default handler for all Google-based loggers + +.. code-block:: console + + export GOOGLE_SDK_PYTHON_LOGGING_SCOPE=google + +- Enabling the default handler for a specific Google module (for a client library called :code:`library_v1`): + +.. code-block:: console + + export GOOGLE_SDK_PYTHON_LOGGING_SCOPE=google.cloud.library_v1 + + +Advanced, code-based configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also configure a valid logging scope using Python's standard `logging` mechanism. + +Code-Based Examples +^^^^^^^^^^^^^^^^^^^ + +- Configuring a handler for all Google-based loggers + +.. code-block:: python + + import logging + + from google.cloud import library_v1 + + base_logger = logging.getLogger("google") + base_logger.addHandler(logging.StreamHandler()) + base_logger.setLevel(logging.DEBUG) + +- Configuring a handler for a specific Google module (for a client library called :code:`library_v1`): + +.. code-block:: python + + import logging + + from google.cloud import library_v1 + + base_logger = logging.getLogger("google.cloud.library_v1") + base_logger.addHandler(logging.StreamHandler()) + base_logger.setLevel(logging.DEBUG) + +Logging details +~~~~~~~~~~~~~~~ + +#. Regardless of which of the mechanisms above you use to configure logging for this library, by default logging events are not propagated up to the root + logger from the `google`-level logger. If you need the events to be propagated to the root logger, you must explicitly set + :code:`logging.getLogger("google").propagate = True` in your code. +#. You can mix the different logging configurations above for different Google modules. For example, you may want use a code-based logging configuration for + one library, but decide you need to also set up environment-based logging configuration for another library. + + #. If you attempt to use both code-based and environment-based configuration for the same module, the environment-based configuration will be ineffectual + if the code -based configuration gets applied first. + +#. The Google-specific logging configurations (default handlers for environment-based configuration; not propagating logging events to the root logger) get + executed the first time *any* client library is instantiated in your application, and only if the affected loggers have not been previously configured. + (This is the reason for 2.i. above.) diff --git a/packages/google-maps-isochrones/docs/_static/custom.css b/packages/google-maps-isochrones/docs/_static/custom.css new file mode 100644 index 000000000000..b0a295464b23 --- /dev/null +++ b/packages/google-maps-isochrones/docs/_static/custom.css @@ -0,0 +1,20 @@ +div#python2-eol { + border-color: red; + border-width: medium; +} + +/* Ensure minimum width for 'Parameters' / 'Returns' column */ +dl.field-list > dt { + min-width: 100px +} + +/* Insert space between methods for readability */ +dl.method { + padding-top: 10px; + padding-bottom: 10px +} + +/* Insert empty space between classes */ +dl.class { + padding-bottom: 50px +} diff --git a/packages/google-maps-isochrones/docs/_templates/layout.html b/packages/google-maps-isochrones/docs/_templates/layout.html new file mode 100644 index 000000000000..95e9c77fcfe1 --- /dev/null +++ b/packages/google-maps-isochrones/docs/_templates/layout.html @@ -0,0 +1,50 @@ + +{% extends "!layout.html" %} +{%- block content %} +{%- if theme_fixed_sidebar|lower == 'true' %} +
+ {{ sidebar() }} + {%- block document %} +
+ {%- if render_sidebar %} +
+ {%- endif %} + + {%- block relbar_top %} + {%- if theme_show_relbar_top|tobool %} + + {%- endif %} + {% endblock %} + +
+
+ As of January 1, 2020 this library no longer supports Python 2 on the latest released version. + Library versions released prior to that date will continue to be available. For more information please + visit Python 2 support on Google Cloud. +
+ {% block body %} {% endblock %} +
+ + {%- block relbar_bottom %} + {%- if theme_show_relbar_bottom|tobool %} + + {%- endif %} + {% endblock %} + + {%- if render_sidebar %} +
+ {%- endif %} +
+ {%- endblock %} +
+
+{%- else %} +{{ super() }} +{%- endif %} +{%- endblock %} diff --git a/packages/google-maps-isochrones/docs/conf.py b/packages/google-maps-isochrones/docs/conf.py new file mode 100644 index 000000000000..36a2a326d272 --- /dev/null +++ b/packages/google-maps-isochrones/docs/conf.py @@ -0,0 +1,417 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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. +## +# google-maps-isochrones documentation build configuration file +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import logging +import os +import shlex +import sys +from typing import Any + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath("..")) + +# For plugins that can not read conf.py. +# See also: https://github.com/docascode/sphinx-docfx-yaml/issues/85 +sys.path.insert(0, os.path.abspath(".")) + +__version__ = "" + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +needs_sphinx = "4.5.0" + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.intersphinx", + "sphinx.ext.coverage", + "sphinx.ext.doctest", + "sphinx.ext.napoleon", + "sphinx.ext.todo", + "sphinx.ext.viewcode", + "recommonmark", +] + +# autodoc/autosummary flags +autoclass_content = "both" +autodoc_default_options = {"members": True} +autosummary_generate = True + + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = [".rst", ".md"] + +# The encoding of source files. +# source_encoding = 'utf-8-sig' + +# The root toctree document. +root_doc = "index" + +# General information about the project. +project = "google-maps-isochrones" +copyright = "2026, Google, LLC" +author = "Google APIs" + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The full version, including alpha/beta/rc tags. +release = __version__ +# The short X.Y version. +version = ".".join(release.split(".")[0:2]) + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [ + "_build", + "**/.nox/**/*", + "samples/AUTHORING_GUIDE.md", + "samples/CONTRIBUTING.md", + "samples/snippets/README.rst", +] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = "alabaster" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +html_theme_options = { + "description": "Google Maps Client Libraries for google-maps-isochrones", + "github_user": "googleapis", + "github_repo": "google-cloud-python", + "github_banner": True, + "font_family": "'Roboto', Georgia, sans", + "head_font_family": "'Roboto', Georgia, serif", + "code_font_family": "'Roboto Mono', 'Consolas', monospace", +} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +# html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +# html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {} + +# If false, no module index is generated. +# html_domain_indices = True + +# If false, no index is generated. +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = "google-maps-isochrones-doc" + +# -- Options for warnings ------------------------------------------------------ + + +suppress_warnings = [ + # Temporarily suppress this to avoid "more than one target found for + # cross-reference" warning, which are intractable for us to avoid while in + # a mono-repo. + # See https://github.com/sphinx-doc/sphinx/blob + # /2a65ffeef5c107c19084fabdd706cdff3f52d93c/sphinx/domains/python.py#L843 + "ref.python" +] + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', + # Latex figure (float) alignment + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ( + root_doc, + "google-maps-isochrones.tex", + "google-maps-isochrones Documentation", + author, + "manual", + ) +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ( + root_doc, + "google-maps-isochrones", + "google-maps-isochrones Documentation", + [author], + 1, + ) +] + +# If true, show URL addresses after external links. +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + root_doc, + "google-maps-isochrones", + "google-maps-isochrones Documentation", + author, + "google-maps-isochrones", + "google-maps-isochrones Library", + "APIs", + ) +] + +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# texinfo_no_detailmenu = False + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = { + "python": ("https://python.readthedocs.org/en/latest/", None), + "google-auth": ("https://googleapis.dev/python/google-auth/latest/", None), + "google.api_core": ( + "https://googleapis.dev/python/google-api-core/latest/", + None, + ), + "grpc": ("https://grpc.github.io/grpc/python/", None), + "proto-plus": ("https://proto-plus-python.readthedocs.io/en/latest/", None), + "protobuf": ("https://googleapis.dev/python/protobuf/latest/", None), +} + + +# Napoleon settings +napoleon_google_docstring = True +napoleon_numpy_docstring = True +napoleon_include_private_with_doc = False +napoleon_include_special_with_doc = True +napoleon_use_admonition_for_examples = False +napoleon_use_admonition_for_notes = False +napoleon_use_admonition_for_references = False +napoleon_use_ivar = False +napoleon_use_param = True +napoleon_use_rtype = True + + +# Setup for sphinx behaviors such as warning filters. +class UnexpectedUnindentFilter(logging.Filter): + """Filter out warnings about unexpected unindentation following bullet lists.""" + + def filter(self, record: logging.LogRecord) -> bool: + """Filter the log record. + + Args: + record (logging.LogRecord): The log record. + + Returns: + bool: False to suppress the warning, True to allow it. + """ + msg = record.getMessage() + if "Bullet list ends without a blank line" in msg: + return False + return True + + +def setup(app: Any) -> None: + """Setup the Sphinx application. + + Args: + app (Any): The Sphinx application. + """ + # Sphinx's logger is hierarchical. Adding a filter to the + # root 'sphinx' logger will catch warnings from all sub-loggers. + logger = logging.getLogger("sphinx") + logger.addFilter(UnexpectedUnindentFilter()) diff --git a/packages/google-maps-isochrones/docs/index.rst b/packages/google-maps-isochrones/docs/index.rst new file mode 100644 index 000000000000..f3fcc96583ce --- /dev/null +++ b/packages/google-maps-isochrones/docs/index.rst @@ -0,0 +1,23 @@ +.. include:: README.rst + +.. include:: multiprocessing.rst + + +API Reference +------------- +.. toctree:: + :maxdepth: 2 + + isochrones_v1/services_ + isochrones_v1/types_ + + +Changelog +--------- + +For a list of all ``google-maps-isochrones`` releases: + +.. toctree:: + :maxdepth: 2 + + CHANGELOG diff --git a/packages/google-maps-isochrones/docs/isochrones_v1/isochrone_service.rst b/packages/google-maps-isochrones/docs/isochrones_v1/isochrone_service.rst new file mode 100644 index 000000000000..718b70e57031 --- /dev/null +++ b/packages/google-maps-isochrones/docs/isochrones_v1/isochrone_service.rst @@ -0,0 +1,6 @@ +IsochroneService +---------------------------------- + +.. automodule:: google.maps.isochrones_v1.services.isochrone_service + :members: + :inherited-members: diff --git a/packages/google-maps-isochrones/docs/isochrones_v1/services_.rst b/packages/google-maps-isochrones/docs/isochrones_v1/services_.rst new file mode 100644 index 000000000000..26ccdb4ef8a2 --- /dev/null +++ b/packages/google-maps-isochrones/docs/isochrones_v1/services_.rst @@ -0,0 +1,6 @@ +Services for Google Maps Isochrones v1 API +========================================== +.. toctree:: + :maxdepth: 2 + + isochrone_service diff --git a/packages/google-maps-isochrones/docs/isochrones_v1/types_.rst b/packages/google-maps-isochrones/docs/isochrones_v1/types_.rst new file mode 100644 index 000000000000..ddaa9fe1b5bd --- /dev/null +++ b/packages/google-maps-isochrones/docs/isochrones_v1/types_.rst @@ -0,0 +1,6 @@ +Types for Google Maps Isochrones v1 API +======================================= + +.. automodule:: google.maps.isochrones_v1.types + :members: + :show-inheritance: diff --git a/packages/google-maps-isochrones/docs/multiprocessing.rst b/packages/google-maps-isochrones/docs/multiprocessing.rst new file mode 100644 index 000000000000..536d17b2ea65 --- /dev/null +++ b/packages/google-maps-isochrones/docs/multiprocessing.rst @@ -0,0 +1,7 @@ +.. note:: + + Because this client uses :mod:`grpc` library, it is safe to + share instances across threads. In multiprocessing scenarios, the best + practice is to create client instances *after* the invocation of + :func:`os.fork` by :class:`multiprocessing.pool.Pool` or + :class:`multiprocessing.Process`. diff --git a/packages/google-maps-isochrones/google/maps/isochrones/__init__.py b/packages/google-maps-isochrones/google/maps/isochrones/__init__.py new file mode 100644 index 000000000000..20d19bd09cec --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones/__init__.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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.maps.isochrones import gapic_version as package_version + +__version__ = package_version.__version__ + + +from google.maps.isochrones_v1.services.isochrone_service.async_client import ( + IsochroneServiceAsyncClient, +) +from google.maps.isochrones_v1.services.isochrone_service.client import ( + IsochroneServiceClient, +) +from google.maps.isochrones_v1.types.isochrones_service import ( + GenerateIsochroneRequest, + GenerateIsochroneResponse, + Isochrone, +) + +__all__ = ( + "IsochroneServiceClient", + "IsochroneServiceAsyncClient", + "GenerateIsochroneRequest", + "GenerateIsochroneResponse", + "Isochrone", +) diff --git a/packages/google-maps-isochrones/google/maps/isochrones/gapic_version.py b/packages/google-maps-isochrones/google/maps/isochrones/gapic_version.py new file mode 100644 index 000000000000..075b8773ece3 --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones/gapic_version.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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.1.0" # {x-release-please-version} diff --git a/packages/google-maps-isochrones/google/maps/isochrones/py.typed b/packages/google-maps-isochrones/google/maps/isochrones/py.typed new file mode 100644 index 000000000000..0c57f147d932 --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones/py.typed @@ -0,0 +1,2 @@ +# Marker file for PEP 561. +# The google-maps-isochrones package uses inline types. diff --git a/packages/google-maps-isochrones/google/maps/isochrones_v1/__init__.py b/packages/google-maps-isochrones/google/maps/isochrones_v1/__init__.py new file mode 100644 index 000000000000..6d2367101db5 --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones_v1/__init__.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 sys + +import google.api_core as api_core + +from google.maps.isochrones_v1 import gapic_version as package_version + +__version__ = package_version.__version__ + +from importlib import metadata + +from .services.isochrone_service import ( + IsochroneServiceAsyncClient, + IsochroneServiceClient, +) +from .types.isochrones_service import ( + GenerateIsochroneRequest, + GenerateIsochroneResponse, + Isochrone, +) + +if hasattr(api_core, "check_python_version") and hasattr( + api_core, "check_dependency_versions" +): # pragma: NO COVER + api_core.check_python_version("google.maps.isochrones_v1") # type: ignore + api_core.check_dependency_versions("google.maps.isochrones_v1") # type: ignore +else: # pragma: NO COVER + # An older version of api_core is installed which does not define the + # functions above. We do equivalent checks manually. + try: + import warnings + + _py_version_str = sys.version.split()[0] + _package_label = "google.maps.isochrones_v1" + if sys.version_info < (3, 10): + warnings.warn( + "You are using a non-supported Python version " + + f"({_py_version_str}). Google will not post any further " + + f"updates to {_package_label} supporting this Python version. " + + "Please upgrade to the latest Python version, or at " + + f"least to Python 3.10, and then update {_package_label}.", + FutureWarning, + ) + + def parse_version_to_tuple(version_string: str): + """Safely converts a semantic version string to a comparable tuple of integers. + Example: "6.33.5" -> (6, 33, 5) + Ignores non-numeric parts and handles common version formats. + Args: + version_string: Version string in the format "x.y.z" or "x.y.z" + Returns: + Tuple of integers for the parsed version string. + """ + parts = [] + for part in version_string.split("."): + try: + parts.append(int(part)) + except ValueError: + # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here. + # This is a simplification compared to 'packaging.parse_version', but sufficient + # for comparing strictly numeric semantic versions. + break + return tuple(parts) + + def _get_version(dependency_name): + try: + version_string: str = metadata.version(dependency_name) + parsed_version = parse_version_to_tuple(version_string) + return (parsed_version, version_string) + except Exception: + # Catch exceptions from metadata.version() (e.g., PackageNotFoundError) + # or errors during parse_version_to_tuple + return (None, "--") + + _dependency_package = "google.protobuf" + _next_supported_version = "6.33.5" + _next_supported_version_tuple = (6, 33, 5) + _recommendation = " (we recommend 7.x)" + (_version_used, _version_used_string) = _get_version(_dependency_package) + if _version_used and _version_used < _next_supported_version_tuple: + warnings.warn( + f"Package {_package_label} depends on " + + f"{_dependency_package}, currently installed at version " + + f"{_version_used_string}. Future updates to " + + f"{_package_label} will require {_dependency_package} at " + + f"version {_next_supported_version} or higher{_recommendation}." + + " Please ensure " + + "that either (a) your Python environment doesn't pin the " + + f"version of {_dependency_package}, so that updates to " + + f"{_package_label} can require the higher version, or " + + "(b) you manually update your Python environment to use at " + + f"least version {_next_supported_version} of " + + f"{_dependency_package}.", + FutureWarning, + ) + except Exception: + warnings.warn( + "Could not determine the version of Python " + + "currently being used. To continue receiving " + + "updates for {_package_label}, ensure you are " + + "using a supported version of Python; see " + + "https://devguide.python.org/versions/" + ) + +__all__ = ( + "IsochroneServiceAsyncClient", + "GenerateIsochroneRequest", + "GenerateIsochroneResponse", + "Isochrone", + "IsochroneServiceClient", +) diff --git a/packages/google-maps-isochrones/google/maps/isochrones_v1/gapic_metadata.json b/packages/google-maps-isochrones/google/maps/isochrones_v1/gapic_metadata.json new file mode 100644 index 000000000000..cc9dc0e01c97 --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones_v1/gapic_metadata.json @@ -0,0 +1,43 @@ + { + "comment": "This file maps proto services/RPCs to the corresponding library clients/methods", + "language": "python", + "libraryPackage": "google.maps.isochrones_v1", + "protoPackage": "google.maps.isochrones.v1", + "schema": "1.0", + "services": { + "IsochroneService": { + "clients": { + "grpc": { + "libraryClient": "IsochroneServiceClient", + "rpcs": { + "GenerateIsochrone": { + "methods": [ + "generate_isochrone" + ] + } + } + }, + "grpc-async": { + "libraryClient": "IsochroneServiceAsyncClient", + "rpcs": { + "GenerateIsochrone": { + "methods": [ + "generate_isochrone" + ] + } + } + }, + "rest": { + "libraryClient": "IsochroneServiceClient", + "rpcs": { + "GenerateIsochrone": { + "methods": [ + "generate_isochrone" + ] + } + } + } + } + } + } +} diff --git a/packages/google-maps-isochrones/google/maps/isochrones_v1/gapic_version.py b/packages/google-maps-isochrones/google/maps/isochrones_v1/gapic_version.py new file mode 100644 index 000000000000..075b8773ece3 --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones_v1/gapic_version.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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.1.0" # {x-release-please-version} diff --git a/packages/google-maps-isochrones/google/maps/isochrones_v1/py.typed b/packages/google-maps-isochrones/google/maps/isochrones_v1/py.typed new file mode 100644 index 000000000000..0c57f147d932 --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones_v1/py.typed @@ -0,0 +1,2 @@ +# Marker file for PEP 561. +# The google-maps-isochrones package uses inline types. diff --git a/packages/google-maps-isochrones/google/maps/isochrones_v1/services/__init__.py b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/__init__.py new file mode 100644 index 000000000000..32b36c5c4fe0 --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/__init__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/__init__.py b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/__init__.py new file mode 100644 index 000000000000..b4a2aacd13f3 --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 IsochroneServiceAsyncClient +from .client import IsochroneServiceClient + +__all__ = ( + "IsochroneServiceClient", + "IsochroneServiceAsyncClient", +) diff --git a/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/async_client.py b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/async_client.py new file mode 100644 index 000000000000..4e5e27dfac37 --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/async_client.py @@ -0,0 +1,399 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 logging as std_logging +import re +from collections import OrderedDict +from typing import ( + Callable, + Dict, + Mapping, + MutableMapping, + MutableSequence, + Optional, + Sequence, + Tuple, + Type, + Union, +) + +import google.protobuf +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 + +from google.maps.isochrones_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.maps.isochrones_v1.types import isochrones_service + +from .client import IsochroneServiceClient +from .transports.base import DEFAULT_CLIENT_INFO, IsochroneServiceTransport +from .transports.grpc_asyncio import IsochroneServiceGrpcAsyncIOTransport + +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 IsochroneServiceAsyncClient: + """Service for calculating isochrones. An isochrone is an area + of reachability from a given origin point within a specified + travel time. + """ + + _client: IsochroneServiceClient + + # Copy defaults from the synchronous client for use here. + # Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead. + DEFAULT_ENDPOINT = IsochroneServiceClient.DEFAULT_ENDPOINT + DEFAULT_MTLS_ENDPOINT = IsochroneServiceClient.DEFAULT_MTLS_ENDPOINT + _DEFAULT_ENDPOINT_TEMPLATE = IsochroneServiceClient._DEFAULT_ENDPOINT_TEMPLATE + _DEFAULT_UNIVERSE = IsochroneServiceClient._DEFAULT_UNIVERSE + + place_path = staticmethod(IsochroneServiceClient.place_path) + parse_place_path = staticmethod(IsochroneServiceClient.parse_place_path) + common_billing_account_path = staticmethod( + IsochroneServiceClient.common_billing_account_path + ) + parse_common_billing_account_path = staticmethod( + IsochroneServiceClient.parse_common_billing_account_path + ) + common_folder_path = staticmethod(IsochroneServiceClient.common_folder_path) + parse_common_folder_path = staticmethod( + IsochroneServiceClient.parse_common_folder_path + ) + common_organization_path = staticmethod( + IsochroneServiceClient.common_organization_path + ) + parse_common_organization_path = staticmethod( + IsochroneServiceClient.parse_common_organization_path + ) + common_project_path = staticmethod(IsochroneServiceClient.common_project_path) + parse_common_project_path = staticmethod( + IsochroneServiceClient.parse_common_project_path + ) + common_location_path = staticmethod(IsochroneServiceClient.common_location_path) + parse_common_location_path = staticmethod( + IsochroneServiceClient.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: + IsochroneServiceAsyncClient: The constructed client. + """ + sa_info_func = ( + IsochroneServiceClient.from_service_account_info.__func__ # type: ignore + ) + return sa_info_func(IsochroneServiceAsyncClient, info, *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: + IsochroneServiceAsyncClient: The constructed client. + """ + sa_file_func = ( + IsochroneServiceClient.from_service_account_file.__func__ # type: ignore + ) + return sa_file_func(IsochroneServiceAsyncClient, filename, *args, **kwargs) + + from_service_account_json = from_service_account_file + + @classmethod + def get_mtls_endpoint_and_cert_source( + cls, client_options: Optional[ClientOptions] = None + ): + """Return the API endpoint and client cert source for mutual TLS. + + The client cert source is determined in the following order: + (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the + client cert source is None. + (2) if `client_options.client_cert_source` is provided, use the provided one; if the + default client cert source exists, use the default one; otherwise the client cert + source is None. + + The API endpoint is determined in the following order: + (1) if `client_options.api_endpoint` if provided, use the provided one. + (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the + default mTLS endpoint; if the environment 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 IsochroneServiceClient.get_mtls_endpoint_and_cert_source(client_options) # type: ignore + + @property + def transport(self) -> IsochroneServiceTransport: + """Returns the transport used by the client instance. + + Returns: + IsochroneServiceTransport: The transport used by the client instance. + """ + return self._client.transport + + @property + def api_endpoint(self) -> str: + """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 = IsochroneServiceClient.get_transport_class + + def __init__( + self, + *, + credentials: Optional[ga_credentials.Credentials] = None, + transport: Optional[ + Union[ + str, IsochroneServiceTransport, Callable[..., IsochroneServiceTransport] + ] + ] = "grpc_asyncio", + client_options: Optional[ClientOptions] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiates the isochrone 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,IsochroneServiceTransport,Callable[..., IsochroneServiceTransport]]]): + 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 IsochroneServiceTransport 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 = IsochroneServiceClient( + 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.maps.isochrones_v1.IsochroneServiceAsyncClient`.", + extra={ + "serviceName": "google.maps.isochrones.v1.IsochroneService", + "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.maps.isochrones.v1.IsochroneService", + "credentialsType": None, + }, + ) + + async def generate_isochrone( + self, + request: Optional[ + Union[isochrones_service.GenerateIsochroneRequest, dict] + ] = None, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> isochrones_service.GenerateIsochroneResponse: + r"""Calculates and returns a single isochrone for a given + set of parameters. + + .. 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.maps import isochrones_v1 + + async def sample_generate_isochrone(): + # Create a client + client = isochrones_v1.IsochroneServiceAsyncClient() + + # Initialize request argument(s) + request = isochrones_v1.GenerateIsochroneRequest( + travel_mode="WALK", + travel_direction="TO", + ) + + # Make the request + response = await client.generate_isochrone(request=request) + + # Handle the response + print(response) + + Args: + request (Optional[Union[google.maps.isochrones_v1.types.GenerateIsochroneRequest, dict]]): + The request object. A request to generate a single + isochrone. + 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.maps.isochrones_v1.types.GenerateIsochroneResponse: + A response containing the generated + isochrone data. + + """ + # 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, isochrones_service.GenerateIsochroneRequest): + request = isochrones_service.GenerateIsochroneRequest(request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._client._transport._wrapped_methods[ + self._client._transport.generate_isochrone + ] + + # 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 __aenter__(self) -> "IsochroneServiceAsyncClient": + 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__ = ("IsochroneServiceAsyncClient",) diff --git a/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/client.py b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/client.py new file mode 100644 index 000000000000..b21804bd3980 --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/client.py @@ -0,0 +1,831 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 os +import re +import warnings +from collections import OrderedDict +from http import HTTPStatus +from typing import ( + Callable, + Dict, + Mapping, + MutableMapping, + MutableSequence, + Optional, + Sequence, + Tuple, + Type, + Union, + cast, +) + +import google.protobuf +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 + +from google.maps.isochrones_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.maps.isochrones_v1.types import isochrones_service + +from .transports.base import DEFAULT_CLIENT_INFO, IsochroneServiceTransport +from .transports.grpc import IsochroneServiceGrpcTransport +from .transports.grpc_asyncio import IsochroneServiceGrpcAsyncIOTransport +from .transports.rest import IsochroneServiceRestTransport + + +class IsochroneServiceClientMeta(type): + """Metaclass for the IsochroneService 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[IsochroneServiceTransport]] + _transport_registry["grpc"] = IsochroneServiceGrpcTransport + _transport_registry["grpc_asyncio"] = IsochroneServiceGrpcAsyncIOTransport + _transport_registry["rest"] = IsochroneServiceRestTransport + + def get_transport_class( + cls, + label: Optional[str] = None, + ) -> Type[IsochroneServiceTransport]: + """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 IsochroneServiceClient(metaclass=IsochroneServiceClientMeta): + """Service for calculating isochrones. An isochrone is an area + of reachability from a given origin point within a specified + travel time. + """ + + @staticmethod + def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]: + """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: + Optional[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) + if m is None: + # Could not parse api_endpoint; return as-is. + return 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 = "isochrones.googleapis.com" + DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__( # type: ignore + DEFAULT_ENDPOINT + ) + + _DEFAULT_ENDPOINT_TEMPLATE = "isochrones.{UNIVERSE_DOMAIN}" + _DEFAULT_UNIVERSE = "googleapis.com" + + @staticmethod + def _use_client_cert_effective(): + """Returns whether client certificate should be used for mTLS if the + google-auth version supports should_use_client_cert automatic mTLS enablement. + + Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. + + Returns: + bool: whether client certificate should be used for mTLS + Raises: + ValueError: (If using a version of google-auth without should_use_client_cert and + GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.) + """ + # check if google-auth version supports should_use_client_cert for automatic mTLS enablement + if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER + return mtls.should_use_client_cert() + else: # pragma: NO COVER + # if unsupported, fallback to reading from env var + use_client_cert_str = os.getenv( + "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false" + ).lower() + if use_client_cert_str not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" + " either `true` or `false`" + ) + return use_client_cert_str == "true" + + @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: + IsochroneServiceClient: 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: + IsochroneServiceClient: 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) -> IsochroneServiceTransport: + """Returns the transport used by the client instance. + + Returns: + IsochroneServiceTransport: The transport used by the client + instance. + """ + return self._transport + + @staticmethod + def place_path( + place_id: str, + ) -> str: + """Returns a fully-qualified place string.""" + return "places/{place_id}".format( + place_id=place_id, + ) + + @staticmethod + def parse_place_path(path: str) -> Dict[str, str]: + """Parses a place path into its component segments.""" + m = re.match(r"^places/(?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 = IsochroneServiceClient._use_client_cert_effective() + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") + 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: + 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 = IsochroneServiceClient._use_client_cert_effective() + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() + universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") + 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, 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 + ) -> str: + """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 = IsochroneServiceClient._DEFAULT_UNIVERSE + if universe_domain != _default_universe: + raise MutualTLSChannelError( + f"mTLS is not supported in any universe other than {_default_universe}." + ) + api_endpoint = IsochroneServiceClient.DEFAULT_MTLS_ENDPOINT + else: + api_endpoint = IsochroneServiceClient._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 = IsochroneServiceClient._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) -> str: + """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, IsochroneServiceTransport, Callable[..., IsochroneServiceTransport] + ] + ] = None, + client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiates the isochrone 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,IsochroneServiceTransport,Callable[..., IsochroneServiceTransport]]]): + 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 IsochroneServiceTransport 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 = ( + IsochroneServiceClient._read_environment_variables() + ) + self._client_cert_source = IsochroneServiceClient._get_client_cert_source( + self._client_options.client_cert_source, self._use_client_cert + ) + self._universe_domain = IsochroneServiceClient._get_universe_domain( + universe_domain_opt, self._universe_domain_env + ) + self._api_endpoint: str = "" # 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, IsochroneServiceTransport) + if transport_provided: + # transport is a IsochroneServiceTransport 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(IsochroneServiceTransport, transport) + self._api_endpoint = self._transport.host + + self._api_endpoint = ( + self._api_endpoint + or IsochroneServiceClient._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[IsochroneServiceTransport], + Callable[..., IsochroneServiceTransport], + ] = ( + IsochroneServiceClient.get_transport_class(transport) + if isinstance(transport, str) or transport is None + else cast(Callable[..., IsochroneServiceTransport], 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.maps.isochrones_v1.IsochroneServiceClient`.", + extra={ + "serviceName": "google.maps.isochrones.v1.IsochroneService", + "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.maps.isochrones.v1.IsochroneService", + "credentialsType": None, + }, + ) + + def generate_isochrone( + self, + request: Optional[ + Union[isochrones_service.GenerateIsochroneRequest, dict] + ] = None, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> isochrones_service.GenerateIsochroneResponse: + r"""Calculates and returns a single isochrone for a given + set of parameters. + + .. 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.maps import isochrones_v1 + + def sample_generate_isochrone(): + # Create a client + client = isochrones_v1.IsochroneServiceClient() + + # Initialize request argument(s) + request = isochrones_v1.GenerateIsochroneRequest( + travel_mode="WALK", + travel_direction="TO", + ) + + # Make the request + response = client.generate_isochrone(request=request) + + # Handle the response + print(response) + + Args: + request (Union[google.maps.isochrones_v1.types.GenerateIsochroneRequest, dict]): + The request object. A request to generate a single + isochrone. + 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.maps.isochrones_v1.types.GenerateIsochroneResponse: + A response containing the generated + isochrone data. + + """ + # 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, isochrones_service.GenerateIsochroneRequest): + request = isochrones_service.GenerateIsochroneRequest(request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.generate_isochrone] + + # 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 __enter__(self) -> "IsochroneServiceClient": + 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__ = ("IsochroneServiceClient",) diff --git a/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/README.rst b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/README.rst new file mode 100644 index 000000000000..e153a53f4c96 --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/README.rst @@ -0,0 +1,10 @@ + +transport inheritance structure +_______________________________ + +``IsochroneServiceTransport`` is the ABC for all transports. + +- public child ``IsochroneServiceGrpcTransport`` for sync gRPC transport (defined in ``grpc.py``). +- public child ``IsochroneServiceGrpcAsyncIOTransport`` for async gRPC transport (defined in ``grpc_asyncio.py``). +- private child ``_BaseIsochroneServiceRestTransport`` for base REST transport with inner classes ``_BaseMETHOD`` (defined in ``rest_base.py``). +- public child ``IsochroneServiceRestTransport`` 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-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/__init__.py b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/__init__.py new file mode 100644 index 000000000000..cad019ab7545 --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/__init__.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 IsochroneServiceTransport +from .grpc import IsochroneServiceGrpcTransport +from .grpc_asyncio import IsochroneServiceGrpcAsyncIOTransport +from .rest import IsochroneServiceRestInterceptor, IsochroneServiceRestTransport + +# Compile a registry of transports. +_transport_registry = OrderedDict() # type: Dict[str, Type[IsochroneServiceTransport]] +_transport_registry["grpc"] = IsochroneServiceGrpcTransport +_transport_registry["grpc_asyncio"] = IsochroneServiceGrpcAsyncIOTransport +_transport_registry["rest"] = IsochroneServiceRestTransport + +__all__ = ( + "IsochroneServiceTransport", + "IsochroneServiceGrpcTransport", + "IsochroneServiceGrpcAsyncIOTransport", + "IsochroneServiceRestTransport", + "IsochroneServiceRestInterceptor", +) diff --git a/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/base.py b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/base.py new file mode 100644 index 000000000000..3e1f408904db --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/base.py @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 +import google.auth # type: ignore +import google.protobuf +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.oauth2 import service_account # type: ignore + +from google.maps.isochrones_v1 import gapic_version as package_version +from google.maps.isochrones_v1.types import isochrones_service + +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 IsochroneServiceTransport(abc.ABC): + """Abstract transport class for IsochroneService.""" + + AUTH_SCOPES = ( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/maps-platform.isochrones", + ) + + DEFAULT_HOST: str = "isochrones.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: 'isochrones.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]): Deprecated. A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. This argument will be + removed in the next major version of this library. + 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. + api_audience (Optional[str]): The intended audience for the API calls + to the service that will be set when using certain 3rd party + authentication flows. Audience is typically a resource identifier. + If not set, the host value will be used as a default. + """ + + # 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=scopes, + quota_project_id=quota_project_id, + default_scopes=self.AUTH_SCOPES, + ) + elif credentials is None and not self._ignore_credentials: + credentials, _ = google.auth.default( + scopes=scopes, + quota_project_id=quota_project_id, + default_scopes=self.AUTH_SCOPES, + ) + # 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 + + self._wrapped_methods: Dict[Callable, Callable] = {} + + @property + def host(self): + return self._host + + def _prep_wrapped_messages(self, client_info): + # Precompute the wrapped methods. + self._wrapped_methods = { + self.generate_isochrone: gapic_v1.method.wrap_method( + self.generate_isochrone, + default_retry=retries.Retry( + initial=1.0, + maximum=10.0, + multiplier=1.3, + predicate=retries.if_exception_type( + core_exceptions.ServiceUnavailable, + ), + deadline=60.0, + ), + default_timeout=60.0, + 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 generate_isochrone( + self, + ) -> Callable[ + [isochrones_service.GenerateIsochroneRequest], + Union[ + isochrones_service.GenerateIsochroneResponse, + Awaitable[isochrones_service.GenerateIsochroneResponse], + ], + ]: + raise NotImplementedError() + + @property + def kind(self) -> str: + raise NotImplementedError() + + +__all__ = ("IsochroneServiceTransport",) diff --git a/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/grpc.py b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/grpc.py new file mode 100644 index 000000000000..16078b89f5d5 --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/grpc.py @@ -0,0 +1,368 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 +import warnings +from typing import Callable, Dict, Optional, Sequence, Tuple, Union + +import google.auth # type: ignore +import google.protobuf.message +import grpc # type: ignore +import proto # type: ignore +from google.api_core import gapic_v1, grpc_helpers +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 + +from google.maps.isochrones_v1.types import isochrones_service + +from .base import DEFAULT_CLIENT_INFO, IsochroneServiceTransport + +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)!r}" + + 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.maps.isochrones.v1.IsochroneService", + "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)!r}" + grpc_response = { + "payload": response_payload, + "metadata": metadata, + "status": "OK", + } + _LOGGER.debug( + f"Received response for {client_call_details.method}.", + extra={ + "serviceName": "google.maps.isochrones.v1.IsochroneService", + "rpcName": client_call_details.method, + "response": grpc_response, + "metadata": grpc_response["metadata"], + }, + ) + return response + + +class IsochroneServiceGrpcTransport(IsochroneServiceTransport): + """gRPC backend transport for IsochroneService. + + Service for calculating isochrones. An isochrone is an area + of reachability from a given origin point within a specified + travel time. + + 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 = "isochrones.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: 'isochrones.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]): Deprecated. 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. + This argument will be removed in the next major version of this library. + 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. + api_audience (Optional[str]): The intended audience for the API calls + to the service that will be set when using certain 3rd party + authentication flows. Audience is typically a resource identifier. + If not set, the host value will be used as a default. + + 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 = "isochrones.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]): Deprecated. A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. This argument will be + removed in the next major version of this library. + 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 generate_isochrone( + self, + ) -> Callable[ + [isochrones_service.GenerateIsochroneRequest], + isochrones_service.GenerateIsochroneResponse, + ]: + r"""Return a callable for the generate isochrone method over gRPC. + + Calculates and returns a single isochrone for a given + set of parameters. + + Returns: + Callable[[~.GenerateIsochroneRequest], + ~.GenerateIsochroneResponse]: + 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 "generate_isochrone" not in self._stubs: + self._stubs["generate_isochrone"] = self._logged_channel.unary_unary( + "/google.maps.isochrones.v1.IsochroneService/GenerateIsochrone", + request_serializer=isochrones_service.GenerateIsochroneRequest.serialize, + response_deserializer=isochrones_service.GenerateIsochroneResponse.deserialize, + ) + return self._stubs["generate_isochrone"] + + def close(self): + self._logged_channel.close() + + @property + def kind(self) -> str: + return "grpc" + + +__all__ = ("IsochroneServiceGrpcTransport",) diff --git a/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/grpc_asyncio.py b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/grpc_asyncio.py new file mode 100644 index 000000000000..26609a43a88b --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/grpc_asyncio.py @@ -0,0 +1,400 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 +import warnings +from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union + +import google.protobuf.message +import grpc # type: ignore +import proto # type: ignore +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 +from grpc.experimental import aio # type: ignore + +from google.maps.isochrones_v1.types import isochrones_service + +from .base import DEFAULT_CLIENT_INFO, IsochroneServiceTransport +from .grpc import IsochroneServiceGrpcTransport + +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)!r}" + + 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.maps.isochrones.v1.IsochroneService", + "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)!r}" + grpc_response = { + "payload": response_payload, + "metadata": metadata, + "status": "OK", + } + _LOGGER.debug( + f"Received response to rpc {client_call_details.method}.", + extra={ + "serviceName": "google.maps.isochrones.v1.IsochroneService", + "rpcName": str(client_call_details.method), + "response": grpc_response, + "metadata": grpc_response["metadata"], + }, + ) + return response + + +class IsochroneServiceGrpcAsyncIOTransport(IsochroneServiceTransport): + """gRPC AsyncIO backend transport for IsochroneService. + + Service for calculating isochrones. An isochrone is an area + of reachability from a given origin point within a specified + travel time. + + 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 = "isochrones.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]): Deprecated. A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. This argument will be + removed in the next major version of this library. + 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 = "isochrones.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: 'isochrones.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]): Deprecated. 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. + This argument will be removed in the next major version of this library. + 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. + api_audience (Optional[str]): The intended audience for the API calls + to the service that will be set when using certain 3rd party + authentication flows. Audience is typically a resource identifier. + If not set, the host value will be used as a default. + + 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 generate_isochrone( + self, + ) -> Callable[ + [isochrones_service.GenerateIsochroneRequest], + Awaitable[isochrones_service.GenerateIsochroneResponse], + ]: + r"""Return a callable for the generate isochrone method over gRPC. + + Calculates and returns a single isochrone for a given + set of parameters. + + Returns: + Callable[[~.GenerateIsochroneRequest], + Awaitable[~.GenerateIsochroneResponse]]: + 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 "generate_isochrone" not in self._stubs: + self._stubs["generate_isochrone"] = self._logged_channel.unary_unary( + "/google.maps.isochrones.v1.IsochroneService/GenerateIsochrone", + request_serializer=isochrones_service.GenerateIsochroneRequest.serialize, + response_deserializer=isochrones_service.GenerateIsochroneResponse.deserialize, + ) + return self._stubs["generate_isochrone"] + + def _prep_wrapped_messages(self, client_info): + """Precompute the wrapped methods, overriding the base class method to use async wrappers.""" + self._wrapped_methods = { + self.generate_isochrone: self._wrap_method( + self.generate_isochrone, + default_retry=retries.AsyncRetry( + initial=1.0, + maximum=10.0, + multiplier=1.3, + predicate=retries.if_exception_type( + core_exceptions.ServiceUnavailable, + ), + deadline=60.0, + ), + default_timeout=60.0, + 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__ = ("IsochroneServiceGrpcAsyncIOTransport",) diff --git a/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/rest.py b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/rest.py new file mode 100644 index 000000000000..4377f8bceed2 --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/rest.py @@ -0,0 +1,412 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 +import warnings +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union + +import google.protobuf +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 +from google.protobuf import json_format +from requests import __version__ as requests_version + +from google.maps.isochrones_v1.types import isochrones_service + +from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO +from .rest_base import _BaseIsochroneServiceRestTransport + +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 IsochroneServiceRestInterceptor: + """Interceptor for IsochroneService. + + 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 IsochroneServiceRestTransport. + + .. code-block:: python + class MyCustomIsochroneServiceInterceptor(IsochroneServiceRestInterceptor): + def pre_generate_isochrone(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_generate_isochrone(self, response): + logging.log(f"Received response: {response}") + return response + + transport = IsochroneServiceRestTransport(interceptor=MyCustomIsochroneServiceInterceptor()) + client = IsochroneServiceClient(transport=transport) + + + """ + + def pre_generate_isochrone( + self, + request: isochrones_service.GenerateIsochroneRequest, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[ + isochrones_service.GenerateIsochroneRequest, + Sequence[Tuple[str, Union[str, bytes]]], + ]: + """Pre-rpc interceptor for generate_isochrone + + Override in a subclass to manipulate the request or metadata + before they are sent to the IsochroneService server. + """ + return request, metadata + + def post_generate_isochrone( + self, response: isochrones_service.GenerateIsochroneResponse + ) -> isochrones_service.GenerateIsochroneResponse: + """Post-rpc interceptor for generate_isochrone + + DEPRECATED. Please use the `post_generate_isochrone_with_metadata` + interceptor instead. + + Override in a subclass to read or manipulate the response + after it is returned by the IsochroneService server but before + it is returned to user code. This `post_generate_isochrone` interceptor runs + before the `post_generate_isochrone_with_metadata` interceptor. + """ + return response + + def post_generate_isochrone_with_metadata( + self, + response: isochrones_service.GenerateIsochroneResponse, + metadata: Sequence[Tuple[str, Union[str, bytes]]], + ) -> Tuple[ + isochrones_service.GenerateIsochroneResponse, + Sequence[Tuple[str, Union[str, bytes]]], + ]: + """Post-rpc interceptor for generate_isochrone + + Override in a subclass to read or manipulate the response or metadata after it + is returned by the IsochroneService server but before it is returned to user code. + + We recommend only using this `post_generate_isochrone_with_metadata` + interceptor in new development instead of the `post_generate_isochrone` interceptor. + When both interceptors are used, this `post_generate_isochrone_with_metadata` interceptor runs after the + `post_generate_isochrone` interceptor. The (possibly modified) response returned by + `post_generate_isochrone` will be passed to + `post_generate_isochrone_with_metadata`. + """ + return response, metadata + + +@dataclasses.dataclass +class IsochroneServiceRestStub: + _session: AuthorizedSession + _host: str + _interceptor: IsochroneServiceRestInterceptor + + +class IsochroneServiceRestTransport(_BaseIsochroneServiceRestTransport): + """REST backend synchronous transport for IsochroneService. + + Service for calculating isochrones. An isochrone is an area + of reachability from a given origin point within a specified + travel time. + + 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 = "isochrones.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[IsochroneServiceRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to (default: 'isochrones.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]): Deprecated. A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. This argument will be + removed in the next major version of this library. + 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. + interceptor (Optional[IsochroneServiceRestInterceptor]): Interceptor used + to manipulate requests, request metadata, and responses. + api_audience (Optional[str]): The intended audience for the API calls + to the service that will be set when using certain 3rd party + authentication flows. Audience is typically a resource identifier. + If not set, the host value will be used as a default. + """ + # 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 IsochroneServiceRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _GenerateIsochrone( + _BaseIsochroneServiceRestTransport._BaseGenerateIsochrone, + IsochroneServiceRestStub, + ): + def __hash__(self): + return hash("IsochroneServiceRestTransport.GenerateIsochrone") + + @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: isochrones_service.GenerateIsochroneRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, Union[str, bytes]]] = (), + ) -> isochrones_service.GenerateIsochroneResponse: + r"""Call the generate isochrone method over HTTP. + + Args: + request (~.isochrones_service.GenerateIsochroneRequest): + The request object. A request to generate a single + isochrone. + 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: + ~.isochrones_service.GenerateIsochroneResponse: + A response containing the generated + isochrone data. + + """ + + http_options = _BaseIsochroneServiceRestTransport._BaseGenerateIsochrone._get_http_options() + + request, metadata = self._interceptor.pre_generate_isochrone( + request, metadata + ) + transcoded_request = _BaseIsochroneServiceRestTransport._BaseGenerateIsochrone._get_transcoded_request( + http_options, request + ) + + body = _BaseIsochroneServiceRestTransport._BaseGenerateIsochrone._get_request_body_json( + transcoded_request + ) + + # Jsonify the query params + query_params = _BaseIsochroneServiceRestTransport._BaseGenerateIsochrone._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.maps.isochrones_v1.IsochroneServiceClient.GenerateIsochrone", + extra={ + "serviceName": "google.maps.isochrones.v1.IsochroneService", + "rpcName": "GenerateIsochrone", + "httpRequest": http_request, + "metadata": http_request["headers"], + }, + ) + + # Send the request + response = IsochroneServiceRestTransport._GenerateIsochrone._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 = isochrones_service.GenerateIsochroneResponse() + pb_resp = isochrones_service.GenerateIsochroneResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + + resp = self._interceptor.post_generate_isochrone(resp) + response_metadata = [(k, str(v)) for k, v in response.headers.items()] + resp, _ = self._interceptor.post_generate_isochrone_with_metadata( + resp, response_metadata + ) + if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( + logging.DEBUG + ): # pragma: NO COVER + try: + response_payload = ( + isochrones_service.GenerateIsochroneResponse.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.maps.isochrones_v1.IsochroneServiceClient.generate_isochrone", + extra={ + "serviceName": "google.maps.isochrones.v1.IsochroneService", + "rpcName": "GenerateIsochrone", + "metadata": http_response["headers"], + "httpResponse": http_response, + }, + ) + return resp + + @property + def generate_isochrone( + self, + ) -> Callable[ + [isochrones_service.GenerateIsochroneRequest], + isochrones_service.GenerateIsochroneResponse, + ]: + # 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._GenerateIsochrone(self._session, self._host, self._interceptor) # type: ignore + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("IsochroneServiceRestTransport",) diff --git a/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/rest_base.py b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/rest_base.py new file mode 100644 index 000000000000..83d03efd7ff8 --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones_v1/services/isochrone_service/transports/rest_base.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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.maps.isochrones_v1.types import isochrones_service + +from .base import DEFAULT_CLIENT_INFO, IsochroneServiceTransport + + +class _BaseIsochroneServiceRestTransport(IsochroneServiceTransport): + """Base REST backend transport for IsochroneService. + + 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 = "isochrones.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: 'isochrones.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 _BaseGenerateIsochrone: + 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": "post", + "uri": "/v1/isochrones:generate", + "body": "*", + }, + ] + return http_options + + @staticmethod + def _get_transcoded_request(http_options, request): + pb_request = isochrones_service.GenerateIsochroneRequest.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( + _BaseIsochroneServiceRestTransport._BaseGenerateIsochrone._get_unset_required_fields( + query_params + ) + ) + + query_params["$alt"] = "json;enum-encoding=int" + return query_params + + +__all__ = ("_BaseIsochroneServiceRestTransport",) diff --git a/packages/google-maps-isochrones/google/maps/isochrones_v1/types/__init__.py b/packages/google-maps-isochrones/google/maps/isochrones_v1/types/__init__.py new file mode 100644 index 000000000000..eb049e97acc7 --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones_v1/types/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 .isochrones_service import ( + GenerateIsochroneRequest, + GenerateIsochroneResponse, + Isochrone, +) + +__all__ = ( + "GenerateIsochroneRequest", + "GenerateIsochroneResponse", + "Isochrone", +) diff --git a/packages/google-maps-isochrones/google/maps/isochrones_v1/types/isochrones_service.py b/packages/google-maps-isochrones/google/maps/isochrones_v1/types/isochrones_service.py new file mode 100644 index 000000000000..f702a7967ad5 --- /dev/null +++ b/packages/google-maps-isochrones/google/maps/isochrones_v1/types/isochrones_service.py @@ -0,0 +1,247 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations + +from typing import MutableMapping, MutableSequence + +import google.protobuf.duration_pb2 as duration_pb2 # type: ignore +import google.protobuf.struct_pb2 as struct_pb2 # type: ignore +import google.type.latlng_pb2 as latlng_pb2 # type: ignore +import proto # type: ignore + +__protobuf__ = proto.module( + package="google.maps.isochrones.v1", + manifest={ + "GenerateIsochroneRequest", + "GenerateIsochroneResponse", + "Isochrone", + }, +) + + +class GenerateIsochroneRequest(proto.Message): + r"""A request to generate a single isochrone. + + 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: + location (google.type.latlng_pb2.LatLng): + The origin as a latitude/longitude + coordinate. + + This field is a member of `oneof`_ ``origin``. + place (str): + The resource name of a place, in the ``places/{place_id}`` + format. + + This field is a member of `oneof`_ ``origin``. + travel_duration (google.protobuf.duration_pb2.Duration): + Required. The travel time for the isochrone + calculation. The value must be positive and is + capped at 7200 seconds (120 minutes). For DRIVE + mode, the maximum allowed duration is 3600 + seconds (60 minutes). + travel_mode (google.maps.isochrones_v1.types.GenerateIsochroneRequest.TravelMode): + Required. The mode of transportation. + travel_direction (google.maps.isochrones_v1.types.GenerateIsochroneRequest.TravelDirection): + Required. The direction of travel. + routing_preference (google.maps.isochrones_v1.types.GenerateIsochroneRequest.RoutingPreference): + Optional. Specifies the preference for how to route. + Defaults to TRAFFIC_UNAWARE. + enable_smoothing (bool): + Optional. Specifies whether to smooth the + edges of the resulting isochrone polygons. + polygon_fidelity (google.maps.isochrones_v1.types.GenerateIsochroneRequest.PolygonFidelity): + Optional. Controls the precision of the generated polygon. + Defaults to POLYGON_FIDELITY_UNSPECIFIED. + """ + + class TravelMode(proto.Enum): + r"""Defines the mode of transportation for isochrone calculation. + + Values: + TRAVEL_MODE_UNSPECIFIED (0): + No travel mode specified. + DRIVE (1): + Travel by passenger car. + BICYCLE (2): + Travel by bicycle. + WALK (3): + Travel by walking. + """ + + TRAVEL_MODE_UNSPECIFIED = 0 + DRIVE = 1 + BICYCLE = 2 + WALK = 3 + + class TravelDirection(proto.Enum): + r"""Specifies the direction of travel for the isochrone + calculation. + + Values: + TRAVEL_DIRECTION_UNSPECIFIED (0): + No travel direction specified. + FROM (1): + Calculates the area reachable *from* the origin point. + Example: "Where can I deliver to from my warehouse in 30 + minutes?". + TO (2): + Calculates the area from which you can travel *to* the + origin point. Example: "Where can my employees commute from + to reach the office in 30 minutes?". + """ + + TRAVEL_DIRECTION_UNSPECIFIED = 0 + FROM = 1 + TO = 2 + + class RoutingPreference(proto.Enum): + r"""Determines how traffic conditions are incorporated into the + calculation. + + Values: + ROUTING_PREFERENCE_UNSPECIFIED (0): + No routing preference specified. The server will use its + default, which is TRAFFIC_UNAWARE. + TRAFFIC_UNAWARE (1): + The calculation will not take traffic + conditions into consideration. The isochrone + will be based on the road network and static + travel times. This is suitable for planning + purposes where traffic is not a factor. + TRAFFIC_AWARE (2): + The calculation will factor in live traffic + conditions. + """ + + ROUTING_PREFERENCE_UNSPECIFIED = 0 + TRAFFIC_UNAWARE = 1 + TRAFFIC_AWARE = 2 + + class PolygonFidelity(proto.Enum): + r"""Controls the level of detail in the isochrone polygon. + + Values: + POLYGON_FIDELITY_UNSPECIFIED (0): + No polygon fidelity specified. The server + will use its default, which is based on the + travel duration. + LOW (1): + Low precision. Good for covering large areas + with fewer vertices. + MEDIUM (2): + Medium precision. A balance between detail + and artifact size. + HIGH (3): + High precision. High fidelity edges, but may + produce holes in the polygon where the road + network density is low. + """ + + POLYGON_FIDELITY_UNSPECIFIED = 0 + LOW = 1 + MEDIUM = 2 + HIGH = 3 + + location: latlng_pb2.LatLng = proto.Field( + proto.MESSAGE, + number=1, + oneof="origin", + message=latlng_pb2.LatLng, + ) + place: str = proto.Field( + proto.STRING, + number=2, + oneof="origin", + ) + travel_duration: duration_pb2.Duration = proto.Field( + proto.MESSAGE, + number=3, + message=duration_pb2.Duration, + ) + travel_mode: TravelMode = proto.Field( + proto.ENUM, + number=4, + enum=TravelMode, + ) + travel_direction: TravelDirection = proto.Field( + proto.ENUM, + number=5, + enum=TravelDirection, + ) + routing_preference: RoutingPreference = proto.Field( + proto.ENUM, + number=6, + enum=RoutingPreference, + ) + enable_smoothing: bool = proto.Field( + proto.BOOL, + number=7, + ) + polygon_fidelity: PolygonFidelity = proto.Field( + proto.ENUM, + number=8, + enum=PolygonFidelity, + ) + + +class GenerateIsochroneResponse(proto.Message): + r"""A response containing the generated isochrone data. + + Attributes: + isochrone (google.maps.isochrones_v1.types.Isochrone): + Output only. The generated isochrone. + """ + + isochrone: "Isochrone" = proto.Field( + proto.MESSAGE, + number=1, + message="Isochrone", + ) + + +class Isochrone(proto.Message): + r"""The result of an isochrone calculation, representing an area + of reachability from an origin point within a specified travel + time. + + + .. _oneof: https://proto-plus-python.readthedocs.io/en/stable/fields.html#oneofs-mutually-exclusive-fields + + Attributes: + geo_json (google.protobuf.struct_pb2.Struct): + Output only. The isochrone geometry in + GeoJSON format, using the RFC 7946 format: + https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.6. + + This field is a member of `oneof`_ ``geometry``. + """ + + geo_json: struct_pb2.Struct = proto.Field( + proto.MESSAGE, + number=1, + oneof="geometry", + message=struct_pb2.Struct, + ) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/packages/google-maps-isochrones/noxfile.py b/packages/google-maps-isochrones/noxfile.py new file mode 100644 index 000000000000..38cb358a930c --- /dev/null +++ b/packages/google-maps-isochrones/noxfile.py @@ -0,0 +1,648 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 +import pathlib +import re +import shutil +import warnings +from typing import Dict, List + +import nox + +RUFF_VERSION = "ruff==0.14.14" + +LINT_PATHS = ["docs", "google", "tests", "noxfile.py", "setup.py"] + +# Add samples to the list of directories to format if the directory exists. +if os.path.isdir("samples"): + LINT_PATHS.append("samples") + +ALL_PYTHON = [ + "3.10", + "3.11", + "3.12", + "3.13", + "3.14", + "3.15", +] + +DEFAULT_PYTHON_VERSION = "3.14" + +PREVIEW_PYTHON_VERSION = "3.15" + +CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) + +if (CURRENT_DIRECTORY / "testing").exists(): + LOWER_BOUND_CONSTRAINTS_FILE = ( + CURRENT_DIRECTORY / "testing" / f"constraints-{ALL_PYTHON[0]}.txt" + ) +else: + LOWER_BOUND_CONSTRAINTS_FILE = CURRENT_DIRECTORY / "constraints.txt" +PACKAGE_NAME = "google-maps-isochrones" + +UNIT_TEST_STANDARD_DEPENDENCIES = [ + "mock", + "asyncmock", + "pytest", + "pytest-cov", + "pytest-asyncio", +] +UNIT_TEST_EXTERNAL_DEPENDENCIES: List[str] = [] +UNIT_TEST_LOCAL_DEPENDENCIES: List[str] = [] +UNIT_TEST_DEPENDENCIES: List[str] = [] +UNIT_TEST_EXTRAS: List[str] = [] +UNIT_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = {} + +SYSTEM_TEST_PYTHON_VERSIONS: List[str] = ALL_PYTHON +SYSTEM_TEST_STANDARD_DEPENDENCIES = [ + "mock", + "pytest", + "google-cloud-testutils", +] +SYSTEM_TEST_EXTERNAL_DEPENDENCIES: List[str] = [] +SYSTEM_TEST_LOCAL_DEPENDENCIES: List[str] = [] +SYSTEM_TEST_DEPENDENCIES: List[str] = [] +SYSTEM_TEST_EXTRAS: List[str] = [] +SYSTEM_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = {} + +nox.options.sessions = [ + "unit", + "system", + "cover", + "lint", + "lint_setup_py", + "blacken", + "docs", +] + +# Error if a python version is missing +nox.options.error_on_missing_interpreters = True + + +@nox.session(python=ALL_PYTHON) +def mypy(session): + """Run the type checker.""" + # TODO(https://github.com/googleapis/gapic-generator-python/issues/2579): + # use the latest version of mypy + session.install( + "mypy<1.16.0", + "types-requests", + "types-protobuf", + ) + session.install(".") + session.run( + "mypy", + f"--config-file={MYPY_CONFIG_FILE}", + "-p", + "google", + "--check-untyped-defs", + *session.posargs, + ) + + +@nox.session +def update_lower_bounds(session): + """Update lower bounds in constraints.txt to match setup.py""" + session.install("google-cloud-testutils") + session.install(".") + + session.run( + "lower-bound-checker", + "update", + "--package-name", + PACKAGE_NAME, + "--constraints-file", + str(LOWER_BOUND_CONSTRAINTS_FILE), + ) + + +@nox.session +def check_lower_bounds(session): + """Check lower bounds in setup.py are reflected in constraints file""" + session.install("google-cloud-testutils") + session.install(".") + + session.run( + "lower-bound-checker", + "check", + "--package-name", + PACKAGE_NAME, + "--constraints-file", + str(LOWER_BOUND_CONSTRAINTS_FILE), + ) + + +@nox.session(python=DEFAULT_PYTHON_VERSION) +def lint(session): + """Run linters. + + Returns a failure if the linters find linting errors or sufficiently + serious code quality issues. + """ + session.install("flake8", RUFF_VERSION) + + # 2. Check formatting + session.run( + "ruff", + "format", + "--check", + f"--target-version=py{ALL_PYTHON[0].replace('.', '')}", + "--line-length=88", + *LINT_PATHS, + ) + + session.run("flake8", "google", "tests") + + +@nox.session(python=DEFAULT_PYTHON_VERSION) +def blacken(session): + """(Deprecated) Legacy session. Please use 'nox -s format'.""" + session.log( + "WARNING: The 'blacken' session is deprecated and will be removed in a future release. Please use 'nox -s format' in the future." + ) + + # Just run the ruff formatter (keeping legacy behavior of only formatting, not sorting imports) + session.install(RUFF_VERSION) + session.run( + "ruff", + "format", + f"--target-version=py{ALL_PYTHON[0].replace('.', '')}", + "--line-length=88", + *LINT_PATHS, + ) + + +@nox.session(python=DEFAULT_PYTHON_VERSION) +def format(session): + """ + Run ruff to sort imports and format code. + """ + # 1. Install ruff (skipped automatically if you run with --no-venv) + session.install(RUFF_VERSION) + + # 2. Run Ruff to fix imports + # check --select I: Enables strict import sorting + # --fix: Applies the changes automatically + session.run( + "ruff", + "check", + "--select", + "I", + "--fix", + f"--target-version=py{ALL_PYTHON[0].replace('.', '')}", + "--line-length=88", # Standard Black line length + *LINT_PATHS, + ) + + # 3. Run Ruff to format code + session.run( + "ruff", + "format", + f"--target-version=py{ALL_PYTHON[0].replace('.', '')}", + "--line-length=88", # Standard Black line length + *LINT_PATHS, + ) + + +@nox.session(python=DEFAULT_PYTHON_VERSION) +def lint_setup_py(session): + """Verify that setup.py is valid (including RST check).""" + session.install("setuptools", "docutils", "pygments") + session.run("python", "setup.py", "check", "--restructuredtext", "--strict") + + +def install_unittest_dependencies(session, *constraints): + standard_deps = UNIT_TEST_STANDARD_DEPENDENCIES + UNIT_TEST_DEPENDENCIES + session.install(*standard_deps, *constraints) + + if UNIT_TEST_EXTERNAL_DEPENDENCIES: + warnings.warn( + "'unit_test_external_dependencies' is deprecated. Instead, please " + "use 'unit_test_dependencies' or 'unit_test_local_dependencies'.", + DeprecationWarning, + ) + session.install(*UNIT_TEST_EXTERNAL_DEPENDENCIES, *constraints) + + if UNIT_TEST_LOCAL_DEPENDENCIES: + session.install(*UNIT_TEST_LOCAL_DEPENDENCIES, *constraints) + + if UNIT_TEST_EXTRAS_BY_PYTHON: + extras = UNIT_TEST_EXTRAS_BY_PYTHON.get(session.python, []) + elif UNIT_TEST_EXTRAS: + extras = UNIT_TEST_EXTRAS + else: + extras = [] + + if extras: + session.install("-e", f".[{','.join(extras)}]", *constraints) + else: + session.install("-e", ".", *constraints) + + +@nox.session(python=ALL_PYTHON) +@nox.parametrize( + "protobuf_implementation", + ["python", "upb"], +) +def unit(session, protobuf_implementation): + # Install all test dependencies, then install this package in-place. + + constraints_path = str( + CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" + ) + install_unittest_dependencies(session, "-c", constraints_path) + + # Run py.test against the unit tests. + session.run( + "py.test", + "--quiet", + f"--junitxml=unit_{session.python}_sponge_log.xml", + "--cov=google", + "--cov=tests/unit", + "--cov-append", + "--cov-config=.coveragerc", + "--cov-report=", + "--cov-fail-under=0", + os.path.join("tests", "unit"), + *session.posargs, + env={ + "PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION": protobuf_implementation, + }, + ) + + +def install_systemtest_dependencies(session, *constraints): + if session.python >= "3.12": + session.install("--pre", "grpcio>=1.75.1") + else: + session.install("--pre", "grpcio<=1.62.2") + + session.install(*SYSTEM_TEST_STANDARD_DEPENDENCIES, *constraints) + + if SYSTEM_TEST_EXTERNAL_DEPENDENCIES: + session.install(*SYSTEM_TEST_EXTERNAL_DEPENDENCIES, *constraints) + + if SYSTEM_TEST_LOCAL_DEPENDENCIES: + session.install("-e", *SYSTEM_TEST_LOCAL_DEPENDENCIES, *constraints) + + if SYSTEM_TEST_DEPENDENCIES: + session.install("-e", *SYSTEM_TEST_DEPENDENCIES, *constraints) + + if SYSTEM_TEST_EXTRAS_BY_PYTHON: + extras = SYSTEM_TEST_EXTRAS_BY_PYTHON.get(session.python, []) + elif SYSTEM_TEST_EXTRAS: + extras = SYSTEM_TEST_EXTRAS + else: + extras = [] + + if extras: + session.install("-e", f".[{','.join(extras)}]", *constraints) + else: + session.install("-e", ".", *constraints) + + +@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS) +def system(session): + """Run the system test suite.""" + constraints_path = str( + CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" + ) + system_test_path = os.path.join("tests", "system.py") + system_test_folder_path = os.path.join("tests", "system") + + # Check the value of `RUN_SYSTEM_TESTS` env var. It defaults to true. + if os.environ.get("RUN_SYSTEM_TESTS", "true") == "false": + session.skip("RUN_SYSTEM_TESTS is set to false, skipping") + # Install pyopenssl for mTLS testing. + if os.environ.get("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true": + session.install("pyopenssl") + + system_test_exists = os.path.exists(system_test_path) + system_test_folder_exists = os.path.exists(system_test_folder_path) + # Sanity check: only run tests if found. + if not system_test_exists and not system_test_folder_exists: + session.skip("System tests were not found") + + install_systemtest_dependencies(session, "-c", constraints_path) + + # Run py.test against the system tests. + if system_test_exists: + session.run( + "py.test", + "--quiet", + f"--junitxml=system_{session.python}_sponge_log.xml", + system_test_path, + *session.posargs, + ) + if system_test_folder_exists: + session.run( + "py.test", + "--quiet", + f"--junitxml=system_{session.python}_sponge_log.xml", + system_test_folder_path, + *session.posargs, + ) + + +@nox.session(python=DEFAULT_PYTHON_VERSION) +def cover(session): + """Run the final coverage report. + + This outputs the coverage report aggregating coverage from the unit + test runs (not system test runs), and then erases coverage data. + """ + session.install("coverage", "pytest-cov") + session.run("coverage", "report", "--show-missing", "--fail-under=100") + + session.run("coverage", "erase") + + +@nox.session(python="3.10") +def docs(session): + """Build the docs for this library.""" + + session.install("-e", ".") + session.install( + # We need to pin to specific versions of the `sphinxcontrib-*` packages + # which still support sphinx 4.x. + # See https://github.com/googleapis/sphinx-docfx-yaml/issues/344 + # and https://github.com/googleapis/sphinx-docfx-yaml/issues/345. + "sphinxcontrib-applehelp==1.0.4", + "sphinxcontrib-devhelp==1.0.2", + "sphinxcontrib-htmlhelp==2.0.1", + "sphinxcontrib-qthelp==1.0.3", + "sphinxcontrib-serializinghtml==1.1.5", + "sphinx==4.5.0", + "alabaster", + "recommonmark", + ) + + shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) + session.run( + "sphinx-build", + "-T", # show full traceback on exception + "-N", # no colors + "-b", + "html", # builder + "-d", + os.path.join("docs", "_build", "doctrees", ""), # cache directory + # paths to build: + os.path.join("docs", ""), + os.path.join("docs", "_build", "html", ""), + ) + + +@nox.session(python="3.10") +def docfx(session): + """Build the docfx yaml files for this library.""" + + session.install("-e", ".") + session.install( + # We need to pin to specific versions of the `sphinxcontrib-*` packages + # which still support sphinx 4.x. + # See https://github.com/googleapis/sphinx-docfx-yaml/issues/344 + # and https://github.com/googleapis/sphinx-docfx-yaml/issues/345. + "sphinxcontrib-applehelp==1.0.4", + "sphinxcontrib-devhelp==1.0.2", + "sphinxcontrib-htmlhelp==2.0.1", + "sphinxcontrib-qthelp==1.0.3", + "sphinxcontrib-serializinghtml==1.1.5", + "gcp-sphinx-docfx-yaml", + "alabaster", + "recommonmark", + ) + + shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) + session.run( + "sphinx-build", + "-T", # show full traceback on exception + "-N", # no colors + "-D", + ( + "extensions=sphinx.ext.autodoc," + "sphinx.ext.autosummary," + "docfx_yaml.extension," + "sphinx.ext.intersphinx," + "sphinx.ext.coverage," + "sphinx.ext.napoleon," + "sphinx.ext.todo," + "sphinx.ext.viewcode," + "recommonmark" + ), + "-b", + "html", + "-d", + os.path.join("docs", "_build", "doctrees", ""), + os.path.join("docs", ""), + os.path.join("docs", "_build", "html", ""), + ) + + +@nox.session(python=PREVIEW_PYTHON_VERSION) +@nox.parametrize( + "protobuf_implementation", + ["python", "upb"], +) +def prerelease_deps(session, protobuf_implementation): + """ + Run all tests with pre-release versions of dependencies installed + rather than the standard non pre-release versions. + Pre-release versions can be installed using + `pip install --pre `. + """ + + # Install all dependencies + session.install("-e", ".") + + # Install dependencies for the unit test environment + unit_deps_all = UNIT_TEST_STANDARD_DEPENDENCIES + UNIT_TEST_EXTERNAL_DEPENDENCIES + session.install(*unit_deps_all) + + # Because we test minimum dependency versions on the minimum Python + # version, the first version we test with in the unit tests sessions has a + # constraints file containing all dependencies and extras. + with open( + CURRENT_DIRECTORY / "testing" / f"constraints-{ALL_PYTHON[0]}.txt", + encoding="utf-8", + ) as constraints_file: + constraints_text = constraints_file.read() + + # Ignore leading whitespace and comment lines. + constraints_deps = [ + match.group(1) + for match in re.finditer( + r"^\s*(\S+)(?===\S+)", constraints_text, flags=re.MULTILINE + ) + ] + + # Install dependencies specified in `testing/constraints-X.txt`. + session.install(*constraints_deps) + + # Note: If a dependency is added to the `prerel_deps` list, + # the `core_dependencies_from_source` list in the `core_deps_from_source` + # nox session should also be updated. + prerel_deps = [ + "googleapis-common-protos", + "google-api-core", + "google-auth", + "grpc-google-iam-v1", + "grpcio>=1.75.1" if session.python >= "3.12" else "grpcio<=1.62.2", + "grpcio-status", + "protobuf", + "proto-plus", + ] + + deps_dir = CURRENT_DIRECTORY.parent + while deps_dir.name != "packages" and deps_dir.parent != deps_dir: + deps_dir = deps_dir.parent + + # Extract the base package name, safely ignoring version bounds and spaces + # (e.g., "grpcio>=1.75.1" becomes "grpcio") + parsed_deps = { + dep: re.match(r"^([a-zA-Z0-9_-]+)", dep).group(1) for dep in prerel_deps + } + + # Dynamically sort local packages vs PyPI dependencies + local_paths = [] + pypi_deps = [] + + for dep, pkg_name in parsed_deps.items(): + if (deps_dir / pkg_name).exists(): + local_paths.append(str(deps_dir / pkg_name)) + else: + pypi_deps.append(dep) + + # Batch pip installations to avoid sequential overhead + if local_paths: + session.install(*local_paths, "--no-deps", "--ignore-installed") + if pypi_deps: + session.install(*pypi_deps, "--pre", "--no-deps", "--ignore-installed") + + # TODO(https://github.com/grpc/grpc/issues/38965): Add `grpcio-status`` + # to the dictionary below once this bug is fixed. + # TODO(https://github.com/googleapis/google-cloud-python/issues/13643): Add + # `googleapis-common-protos` and `grpc-google-iam-v1` to the dictionary below + # once this bug is fixed. + package_namespaces = { + "google-api-core": "google.api_core", + "google-auth": "google.auth", + "grpcio": "grpc", + "protobuf": "google.protobuf", + "proto-plus": "proto", + } + + # Reuse the parsed names for logging and version verification + for dep, pkg_name in parsed_deps.items(): + print(f"Installed {dep}") + version_namespace = package_namespaces.get(pkg_name) + + if version_namespace: + session.run( + "python", + "-c", + f"import {version_namespace}; print({version_namespace}.__version__)", + ) + + session.run( + "py.test", + "tests/unit", + env={ + "PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION": protobuf_implementation, + }, + ) + + +@nox.session(python=PREVIEW_PYTHON_VERSION) +@nox.parametrize( + "protobuf_implementation", + ["python", "upb"], +) +def core_deps_from_source(session, protobuf_implementation): + """Run all tests with core dependencies installed from source + rather than pulling the dependencies from PyPI. + """ + + # Install all dependencies + session.install("-e", ".") + + # Install dependencies for the unit test environment + unit_deps_all = UNIT_TEST_STANDARD_DEPENDENCIES + UNIT_TEST_EXTERNAL_DEPENDENCIES + session.install(*unit_deps_all) + + # Because we test minimum dependency versions on the minimum Python + # version, the first version we test with in the unit tests sessions has a + # constraints file containing all dependencies and extras. + with open( + CURRENT_DIRECTORY / "testing" / f"constraints-{ALL_PYTHON[0]}.txt", + encoding="utf-8", + ) as constraints_file: + constraints_text = constraints_file.read() + + # Ignore leading whitespace and comment lines. + constraints_deps = [ + match.group(1) + for match in re.finditer( + r"^\s*(\S+)(?===\S+)", constraints_text, flags=re.MULTILINE + ) + ] + + # Install dependencies specified in `testing/constraints-X.txt`. + session.install(*constraints_deps) + + # TODO(https://github.com/googleapis/gapic-generator-python/issues/2358): `grpcio` and + # `grpcio-status` should be added to the list below so that they are installed from source, + # rather than PyPI. + # TODO(https://github.com/googleapis/gapic-generator-python/issues/2357): `protobuf` should be + # added to the list below so that it is installed from source, rather than PyPI + # Note: If a dependency is added to the `core_dependencies_from_source` list, + # the `prerel_deps` list in the `prerelease_deps` nox session should also be updated. + core_dependencies_from_source = [ + "googleapis-common-protos", + "google-api-core", + "google-auth", + "grpc-google-iam-v1", + "proto-plus", + ] + + deps_dir = CURRENT_DIRECTORY.parent + while deps_dir.name != "packages" and deps_dir.parent != deps_dir: + deps_dir = deps_dir.parent + + # Batch the pip installation to avoid sequential overhead + dep_paths = [str(deps_dir / dep) for dep in core_dependencies_from_source] + + session.install(*dep_paths, "--no-deps", "--ignore-installed") + print( + f"Installed {', '.join(core_dependencies_from_source)} locally from {deps_dir}" + ) + + session.run( + "py.test", + "tests/unit", + env={ + "PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION": protobuf_implementation, + }, + ) diff --git a/packages/google-maps-isochrones/samples/generated_samples/isochrones_v1_generated_isochrone_service_generate_isochrone_async.py b/packages/google-maps-isochrones/samples/generated_samples/isochrones_v1_generated_isochrone_service_generate_isochrone_async.py new file mode 100644 index 000000000000..ac385d992a71 --- /dev/null +++ b/packages/google-maps-isochrones/samples/generated_samples/isochrones_v1_generated_isochrone_service_generate_isochrone_async.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 GenerateIsochrone +# 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-maps-isochrones + + +# [START isochrones_v1_generated_IsochroneService_GenerateIsochrone_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.maps import isochrones_v1 + + +async def sample_generate_isochrone(): + # Create a client + client = isochrones_v1.IsochroneServiceAsyncClient() + + # Initialize request argument(s) + request = isochrones_v1.GenerateIsochroneRequest( + travel_mode="WALK", + travel_direction="TO", + ) + + # Make the request + response = await client.generate_isochrone(request=request) + + # Handle the response + print(response) + + +# [END isochrones_v1_generated_IsochroneService_GenerateIsochrone_async] diff --git a/packages/google-maps-isochrones/samples/generated_samples/isochrones_v1_generated_isochrone_service_generate_isochrone_sync.py b/packages/google-maps-isochrones/samples/generated_samples/isochrones_v1_generated_isochrone_service_generate_isochrone_sync.py new file mode 100644 index 000000000000..6eef062d093c --- /dev/null +++ b/packages/google-maps-isochrones/samples/generated_samples/isochrones_v1_generated_isochrone_service_generate_isochrone_sync.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 GenerateIsochrone +# 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-maps-isochrones + + +# [START isochrones_v1_generated_IsochroneService_GenerateIsochrone_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.maps import isochrones_v1 + + +def sample_generate_isochrone(): + # Create a client + client = isochrones_v1.IsochroneServiceClient() + + # Initialize request argument(s) + request = isochrones_v1.GenerateIsochroneRequest( + travel_mode="WALK", + travel_direction="TO", + ) + + # Make the request + response = client.generate_isochrone(request=request) + + # Handle the response + print(response) + + +# [END isochrones_v1_generated_IsochroneService_GenerateIsochrone_sync] diff --git a/packages/google-maps-isochrones/samples/generated_samples/snippet_metadata_google.maps.isochrones.v1.json b/packages/google-maps-isochrones/samples/generated_samples/snippet_metadata_google.maps.isochrones.v1.json new file mode 100644 index 000000000000..a8c8bb257328 --- /dev/null +++ b/packages/google-maps-isochrones/samples/generated_samples/snippet_metadata_google.maps.isochrones.v1.json @@ -0,0 +1,168 @@ +{ + "clientLibrary": { + "apis": [ + { + "id": "google.maps.isochrones.v1", + "version": "v1" + } + ], + "language": "PYTHON", + "name": "google-maps-isochrones", + "version": "0.1.0" + }, + "snippets": [ + { + "canonical": true, + "clientMethod": { + "async": true, + "client": { + "fullName": "google.maps.isochrones_v1.IsochroneServiceAsyncClient", + "shortName": "IsochroneServiceAsyncClient" + }, + "fullName": "google.maps.isochrones_v1.IsochroneServiceAsyncClient.generate_isochrone", + "method": { + "fullName": "google.maps.isochrones.v1.IsochroneService.GenerateIsochrone", + "service": { + "fullName": "google.maps.isochrones.v1.IsochroneService", + "shortName": "IsochroneService" + }, + "shortName": "GenerateIsochrone" + }, + "parameters": [ + { + "name": "request", + "type": "google.maps.isochrones_v1.types.GenerateIsochroneRequest" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "resultType": "google.maps.isochrones_v1.types.GenerateIsochroneResponse", + "shortName": "generate_isochrone" + }, + "description": "Sample for GenerateIsochrone", + "file": "isochrones_v1_generated_isochrone_service_generate_isochrone_async.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "isochrones_v1_generated_IsochroneService_GenerateIsochrone_async", + "segments": [ + { + "end": 52, + "start": 27, + "type": "FULL" + }, + { + "end": 52, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 46, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 49, + "start": 47, + "type": "REQUEST_EXECUTION" + }, + { + "end": 53, + "start": 50, + "type": "RESPONSE_HANDLING" + } + ], + "title": "isochrones_v1_generated_isochrone_service_generate_isochrone_async.py" + }, + { + "canonical": true, + "clientMethod": { + "client": { + "fullName": "google.maps.isochrones_v1.IsochroneServiceClient", + "shortName": "IsochroneServiceClient" + }, + "fullName": "google.maps.isochrones_v1.IsochroneServiceClient.generate_isochrone", + "method": { + "fullName": "google.maps.isochrones.v1.IsochroneService.GenerateIsochrone", + "service": { + "fullName": "google.maps.isochrones.v1.IsochroneService", + "shortName": "IsochroneService" + }, + "shortName": "GenerateIsochrone" + }, + "parameters": [ + { + "name": "request", + "type": "google.maps.isochrones_v1.types.GenerateIsochroneRequest" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, Union[str, bytes]]]" + } + ], + "resultType": "google.maps.isochrones_v1.types.GenerateIsochroneResponse", + "shortName": "generate_isochrone" + }, + "description": "Sample for GenerateIsochrone", + "file": "isochrones_v1_generated_isochrone_service_generate_isochrone_sync.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "isochrones_v1_generated_IsochroneService_GenerateIsochrone_sync", + "segments": [ + { + "end": 52, + "start": 27, + "type": "FULL" + }, + { + "end": 52, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 46, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 49, + "start": 47, + "type": "REQUEST_EXECUTION" + }, + { + "end": 53, + "start": 50, + "type": "RESPONSE_HANDLING" + } + ], + "title": "isochrones_v1_generated_isochrone_service_generate_isochrone_sync.py" + } + ] +} diff --git a/packages/google-maps-isochrones/setup.py b/packages/google-maps-isochrones/setup.py new file mode 100644 index 000000000000..c144e0ec6674 --- /dev/null +++ b/packages/google-maps-isochrones/setup.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 io +import os +import re + +import setuptools # type: ignore + +package_root = os.path.abspath(os.path.dirname(__file__)) + +name = "google-maps-isochrones" + + +description = "Google Maps Isochrones API client library" + +version = None + +with open(os.path.join(package_root, "google/maps/isochrones/gapic_version.py")) as fp: + version_candidates = re.findall( + r"(?<=\")\d+\.\d+\.\d+[^\"\s]*(?=\")", + fp.read(), + ) + assert len(version_candidates) == 1 + version = version_candidates[0] + +if version[0] == "0": + release_status = "Development Status :: 4 - Beta" +else: + release_status = "Development Status :: 5 - Production/Stable" + +dependencies = [ + "google-api-core[grpc] >= 2.24.2, <3.0.0", + # Exclude incompatible versions of `google-auth` + # See https://github.com/googleapis/google-cloud-python/issues/12364 + "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", + "grpcio >= 1.59.0, < 2.0.0", + "grpcio >= 1.75.1, < 2.0.0; python_version >= '3.14'", + "proto-plus >= 1.26.1, <2.0.0", + "protobuf >= 6.33.5, < 8.0.0", +] +extras = {} +url = "https://github.com/googleapis/google-cloud-python/tree/main/packages/google-maps-isochrones" + +package_root = os.path.abspath(os.path.dirname(__file__)) + +readme_filename = os.path.join(package_root, "README.rst") +with io.open(readme_filename, encoding="utf-8") as readme_file: + readme = readme_file.read() + +packages = [ + package + for package in setuptools.find_namespace_packages() + if package.startswith("google") +] + +setuptools.setup( + name=name, + version=version, + description=description, + long_description=readme, + author="Google LLC", + author_email="googleapis-packages@google.com", + license="Apache-2.0", + url=url, + classifiers=[ + release_status, + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Operating System :: OS Independent", + "Topic :: Internet", + ], + platforms="Posix; MacOS X; Windows", + packages=packages, + python_requires=">=3.10", + install_requires=dependencies, + extras_require=extras, + include_package_data=True, + zip_safe=False, +) diff --git a/packages/google-maps-isochrones/testing/constraints-3.10.txt b/packages/google-maps-isochrones/testing/constraints-3.10.txt new file mode 100644 index 000000000000..81605a716d32 --- /dev/null +++ b/packages/google-maps-isochrones/testing/constraints-3.10.txt @@ -0,0 +1,11 @@ +# This constraints file is used to check that lower bounds +# are correct in setup.py +# List all library dependencies and extras in this file, +# pinning their versions to their lower bounds. +# For example, if setup.py has "google-cloud-foo >= 1.14.0, < 2.0.0", +# then this file should have google-cloud-foo==1.14.0 +google-api-core==2.24.2 +google-auth==2.14.1 +grpcio==1.59.0 +proto-plus==1.26.1 +protobuf==6.33.5 diff --git a/packages/google-maps-isochrones/testing/constraints-3.11.txt b/packages/google-maps-isochrones/testing/constraints-3.11.txt new file mode 100644 index 000000000000..7599dea499ed --- /dev/null +++ b/packages/google-maps-isochrones/testing/constraints-3.11.txt @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# This constraints file is required for unit tests. +# List all library dependencies and extras in this file. +google-api-core +google-auth +grpcio +proto-plus +protobuf +# cryptography is a direct dependency of google-auth +cryptography diff --git a/packages/google-maps-isochrones/testing/constraints-3.12.txt b/packages/google-maps-isochrones/testing/constraints-3.12.txt new file mode 100644 index 000000000000..7599dea499ed --- /dev/null +++ b/packages/google-maps-isochrones/testing/constraints-3.12.txt @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# This constraints file is required for unit tests. +# List all library dependencies and extras in this file. +google-api-core +google-auth +grpcio +proto-plus +protobuf +# cryptography is a direct dependency of google-auth +cryptography diff --git a/packages/google-maps-isochrones/testing/constraints-3.13.txt b/packages/google-maps-isochrones/testing/constraints-3.13.txt new file mode 100644 index 000000000000..6bd7e1f5b03d --- /dev/null +++ b/packages/google-maps-isochrones/testing/constraints-3.13.txt @@ -0,0 +1,12 @@ +# We use the constraints file for the latest Python version +# (currently this file) to check that the latest +# major versions of dependencies are supported in setup.py. +# List all library dependencies and extras in this file. +# Require the latest major version be installed for each dependency. +# e.g., if setup.py has "google-cloud-foo >= 1.14.0, < 2.0.0", +# Then this file should have google-cloud-foo>=1 +google-api-core>=2 +google-auth>=2 +grpcio>=1 +proto-plus>=1 +protobuf>=7 diff --git a/packages/google-maps-isochrones/testing/constraints-3.14.txt b/packages/google-maps-isochrones/testing/constraints-3.14.txt new file mode 100644 index 000000000000..6bd7e1f5b03d --- /dev/null +++ b/packages/google-maps-isochrones/testing/constraints-3.14.txt @@ -0,0 +1,12 @@ +# We use the constraints file for the latest Python version +# (currently this file) to check that the latest +# major versions of dependencies are supported in setup.py. +# List all library dependencies and extras in this file. +# Require the latest major version be installed for each dependency. +# e.g., if setup.py has "google-cloud-foo >= 1.14.0, < 2.0.0", +# Then this file should have google-cloud-foo>=1 +google-api-core>=2 +google-auth>=2 +grpcio>=1 +proto-plus>=1 +protobuf>=7 diff --git a/packages/google-maps-isochrones/testing/constraints-3.15.txt b/packages/google-maps-isochrones/testing/constraints-3.15.txt new file mode 100644 index 000000000000..6bd7e1f5b03d --- /dev/null +++ b/packages/google-maps-isochrones/testing/constraints-3.15.txt @@ -0,0 +1,12 @@ +# We use the constraints file for the latest Python version +# (currently this file) to check that the latest +# major versions of dependencies are supported in setup.py. +# List all library dependencies and extras in this file. +# Require the latest major version be installed for each dependency. +# e.g., if setup.py has "google-cloud-foo >= 1.14.0, < 2.0.0", +# Then this file should have google-cloud-foo>=1 +google-api-core>=2 +google-auth>=2 +grpcio>=1 +proto-plus>=1 +protobuf>=7 diff --git a/packages/google-maps-isochrones/tests/__init__.py b/packages/google-maps-isochrones/tests/__init__.py new file mode 100644 index 000000000000..32b36c5c4fe0 --- /dev/null +++ b/packages/google-maps-isochrones/tests/__init__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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-maps-isochrones/tests/unit/__init__.py b/packages/google-maps-isochrones/tests/unit/__init__.py new file mode 100644 index 000000000000..32b36c5c4fe0 --- /dev/null +++ b/packages/google-maps-isochrones/tests/unit/__init__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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-maps-isochrones/tests/unit/gapic/__init__.py b/packages/google-maps-isochrones/tests/unit/gapic/__init__.py new file mode 100644 index 000000000000..32b36c5c4fe0 --- /dev/null +++ b/packages/google-maps-isochrones/tests/unit/gapic/__init__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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-maps-isochrones/tests/unit/gapic/isochrones_v1/__init__.py b/packages/google-maps-isochrones/tests/unit/gapic/isochrones_v1/__init__.py new file mode 100644 index 000000000000..32b36c5c4fe0 --- /dev/null +++ b/packages/google-maps-isochrones/tests/unit/gapic/isochrones_v1/__init__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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-maps-isochrones/tests/unit/gapic/isochrones_v1/test_isochrone_service.py b/packages/google-maps-isochrones/tests/unit/gapic/isochrones_v1/test_isochrone_service.py new file mode 100644 index 000000000000..d3bd76858cbc --- /dev/null +++ b/packages/google-maps-isochrones/tests/unit/gapic/isochrones_v1/test_isochrone_service.py @@ -0,0 +1,2681 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 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 asyncio +import json +import math +import os +from collections.abc import AsyncIterable, Iterable, Mapping, Sequence +from unittest import mock +from unittest.mock import AsyncMock + +import grpc +import pytest +from google.api_core import api_core_version +from google.protobuf import json_format +from grpc.experimental import aio +from proto.marshal.rules import wrappers +from proto.marshal.rules.dates import DurationRule, TimestampRule +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 + +import google.auth +import google.protobuf.duration_pb2 as duration_pb2 # type: ignore +import google.type.latlng_pb2 as latlng_pb2 # type: ignore +from google.api_core import ( + client_options, + gapic_v1, + grpc_helpers, + grpc_helpers_async, + path_template, +) +from google.api_core import exceptions as core_exceptions +from google.api_core import retry as retries +from google.auth import credentials as ga_credentials +from google.auth.exceptions import MutualTLSChannelError +from google.oauth2 import service_account + +from google.maps.isochrones_v1.services.isochrone_service import ( + IsochroneServiceAsyncClient, + IsochroneServiceClient, + transports, +) +from google.maps.isochrones_v1.types import isochrones_service + +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 + ) + + +@pytest.fixture(autouse=True) +def set_event_loop(): + try: + asyncio.get_running_loop() + yield + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + try: + yield + finally: + loop.close() + asyncio.set_event_loop(None) + + +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" + custom_endpoint = ".custom" + + assert IsochroneServiceClient._get_default_mtls_endpoint(None) is None + assert ( + IsochroneServiceClient._get_default_mtls_endpoint(api_endpoint) + == api_mtls_endpoint + ) + assert ( + IsochroneServiceClient._get_default_mtls_endpoint(api_mtls_endpoint) + == api_mtls_endpoint + ) + assert ( + IsochroneServiceClient._get_default_mtls_endpoint(sandbox_endpoint) + == sandbox_mtls_endpoint + ) + assert ( + IsochroneServiceClient._get_default_mtls_endpoint(sandbox_mtls_endpoint) + == sandbox_mtls_endpoint + ) + assert ( + IsochroneServiceClient._get_default_mtls_endpoint(non_googleapi) + == non_googleapi + ) + assert ( + IsochroneServiceClient._get_default_mtls_endpoint(custom_endpoint) + == custom_endpoint + ) + + +def test__read_environment_variables(): + assert IsochroneServiceClient._read_environment_variables() == (False, "auto", None) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + assert IsochroneServiceClient._read_environment_variables() == ( + True, + "auto", + None, + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): + assert IsochroneServiceClient._read_environment_variables() == ( + False, + "auto", + None, + ) + + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with pytest.raises(ValueError) as excinfo: + IsochroneServiceClient._read_environment_variables() + assert ( + str(excinfo.value) + == "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) + else: + assert IsochroneServiceClient._read_environment_variables() == ( + False, + "auto", + None, + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + assert IsochroneServiceClient._read_environment_variables() == ( + False, + "never", + None, + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + assert IsochroneServiceClient._read_environment_variables() == ( + False, + "always", + None, + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}): + assert IsochroneServiceClient._read_environment_variables() == ( + False, + "auto", + None, + ) + + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError) as excinfo: + IsochroneServiceClient._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 IsochroneServiceClient._read_environment_variables() == ( + False, + "auto", + "foo.com", + ) + + +def test_use_client_cert_effective(): + # Test case 1: Test when `should_use_client_cert` returns True. + # We mock the `should_use_client_cert` function to simulate a scenario where + # the google-auth library supports automatic mTLS and determines that a + # client certificate should be used. + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch( + "google.auth.transport.mtls.should_use_client_cert", return_value=True + ): + assert IsochroneServiceClient._use_client_cert_effective() is True + + # Test case 2: Test when `should_use_client_cert` returns False. + # We mock the `should_use_client_cert` function to simulate a scenario where + # the google-auth library supports automatic mTLS and determines that a + # client certificate should NOT be used. + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch( + "google.auth.transport.mtls.should_use_client_cert", return_value=False + ): + assert IsochroneServiceClient._use_client_cert_effective() is False + + # Test case 3: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + assert IsochroneServiceClient._use_client_cert_effective() is True + + # Test case 4: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "false". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"} + ): + assert IsochroneServiceClient._use_client_cert_effective() is False + + # Test case 5: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "True". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "True"}): + assert IsochroneServiceClient._use_client_cert_effective() is True + + # Test case 6: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "False". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "False"} + ): + assert IsochroneServiceClient._use_client_cert_effective() is False + + # Test case 7: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "TRUE". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "TRUE"}): + assert IsochroneServiceClient._use_client_cert_effective() is True + + # Test case 8: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "FALSE". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "FALSE"} + ): + assert IsochroneServiceClient._use_client_cert_effective() is False + + # Test case 9: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not set. + # In this case, the method should return False, which is the default value. + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, clear=True): + assert IsochroneServiceClient._use_client_cert_effective() is False + + # Test case 10: Test when `should_use_client_cert` is unavailable and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. + # The method should raise a ValueError as the environment variable must be either + # "true" or "false". + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"} + ): + with pytest.raises(ValueError): + IsochroneServiceClient._use_client_cert_effective() + + # Test case 11: Test when `should_use_client_cert` is available and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to an invalid value. + # The method should return False as the environment variable is set to an invalid value. + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "unsupported"} + ): + assert IsochroneServiceClient._use_client_cert_effective() is False + + # Test case 12: Test when `should_use_client_cert` is available and the + # `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset. Also, + # the GOOGLE_API_CONFIG environment variable is unset. + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": ""}): + with mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}): + assert IsochroneServiceClient._use_client_cert_effective() is False + + +def test__get_client_cert_source(): + mock_provided_cert_source = mock.Mock() + mock_default_cert_source = mock.Mock() + + assert IsochroneServiceClient._get_client_cert_source(None, False) is None + assert ( + IsochroneServiceClient._get_client_cert_source(mock_provided_cert_source, False) + is None + ) + assert ( + IsochroneServiceClient._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 ( + IsochroneServiceClient._get_client_cert_source(None, True) + is mock_default_cert_source + ) + assert ( + IsochroneServiceClient._get_client_cert_source( + mock_provided_cert_source, "true" + ) + is mock_provided_cert_source + ) + + +@mock.patch.object( + IsochroneServiceClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(IsochroneServiceClient), +) +@mock.patch.object( + IsochroneServiceAsyncClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(IsochroneServiceAsyncClient), +) +def test__get_api_endpoint(): + api_override = "foo.com" + mock_client_cert_source = mock.Mock() + default_universe = IsochroneServiceClient._DEFAULT_UNIVERSE + default_endpoint = IsochroneServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=default_universe + ) + mock_universe = "bar.com" + mock_endpoint = IsochroneServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=mock_universe + ) + + assert ( + IsochroneServiceClient._get_api_endpoint( + api_override, mock_client_cert_source, default_universe, "always" + ) + == api_override + ) + assert ( + IsochroneServiceClient._get_api_endpoint( + None, mock_client_cert_source, default_universe, "auto" + ) + == IsochroneServiceClient.DEFAULT_MTLS_ENDPOINT + ) + assert ( + IsochroneServiceClient._get_api_endpoint(None, None, default_universe, "auto") + == default_endpoint + ) + assert ( + IsochroneServiceClient._get_api_endpoint(None, None, default_universe, "always") + == IsochroneServiceClient.DEFAULT_MTLS_ENDPOINT + ) + assert ( + IsochroneServiceClient._get_api_endpoint( + None, mock_client_cert_source, default_universe, "always" + ) + == IsochroneServiceClient.DEFAULT_MTLS_ENDPOINT + ) + assert ( + IsochroneServiceClient._get_api_endpoint(None, None, mock_universe, "never") + == mock_endpoint + ) + assert ( + IsochroneServiceClient._get_api_endpoint(None, None, default_universe, "never") + == default_endpoint + ) + + with pytest.raises(MutualTLSChannelError) as excinfo: + IsochroneServiceClient._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 ( + IsochroneServiceClient._get_universe_domain( + client_universe_domain, universe_domain_env + ) + == client_universe_domain + ) + assert ( + IsochroneServiceClient._get_universe_domain(None, universe_domain_env) + == universe_domain_env + ) + assert ( + IsochroneServiceClient._get_universe_domain(None, None) + == IsochroneServiceClient._DEFAULT_UNIVERSE + ) + + with pytest.raises(ValueError) as excinfo: + IsochroneServiceClient._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 = IsochroneServiceClient(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 = IsochroneServiceClient(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", + [ + (IsochroneServiceClient, "grpc"), + (IsochroneServiceAsyncClient, "grpc_asyncio"), + (IsochroneServiceClient, "rest"), + ], +) +def test_isochrone_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 == ( + "isochrones.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://isochrones.googleapis.com" + ) + + +@pytest.mark.parametrize( + "transport_class,transport_name", + [ + (transports.IsochroneServiceGrpcTransport, "grpc"), + (transports.IsochroneServiceGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.IsochroneServiceRestTransport, "rest"), + ], +) +def test_isochrone_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", + [ + (IsochroneServiceClient, "grpc"), + (IsochroneServiceAsyncClient, "grpc_asyncio"), + (IsochroneServiceClient, "rest"), + ], +) +def test_isochrone_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 == ( + "isochrones.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://isochrones.googleapis.com" + ) + + +def test_isochrone_service_client_get_transport_class(): + transport = IsochroneServiceClient.get_transport_class() + available_transports = [ + transports.IsochroneServiceGrpcTransport, + transports.IsochroneServiceRestTransport, + ] + assert transport in available_transports + + transport = IsochroneServiceClient.get_transport_class("grpc") + assert transport == transports.IsochroneServiceGrpcTransport + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (IsochroneServiceClient, transports.IsochroneServiceGrpcTransport, "grpc"), + ( + IsochroneServiceAsyncClient, + transports.IsochroneServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + (IsochroneServiceClient, transports.IsochroneServiceRestTransport, "rest"), + ], +) +@mock.patch.object( + IsochroneServiceClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(IsochroneServiceClient), +) +@mock.patch.object( + IsochroneServiceAsyncClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(IsochroneServiceAsyncClient), +) +def test_isochrone_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(IsochroneServiceClient, "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(IsochroneServiceClient, "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 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", + [ + ( + IsochroneServiceClient, + transports.IsochroneServiceGrpcTransport, + "grpc", + "true", + ), + ( + IsochroneServiceAsyncClient, + transports.IsochroneServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + ( + IsochroneServiceClient, + transports.IsochroneServiceGrpcTransport, + "grpc", + "false", + ), + ( + IsochroneServiceAsyncClient, + transports.IsochroneServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ( + IsochroneServiceClient, + transports.IsochroneServiceRestTransport, + "rest", + "true", + ), + ( + IsochroneServiceClient, + transports.IsochroneServiceRestTransport, + "rest", + "false", + ), + ], +) +@mock.patch.object( + IsochroneServiceClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(IsochroneServiceClient), +) +@mock.patch.object( + IsochroneServiceAsyncClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(IsochroneServiceAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_isochrone_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", [IsochroneServiceClient, IsochroneServiceAsyncClient] +) +@mock.patch.object( + IsochroneServiceClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(IsochroneServiceClient), +) +@mock.patch.object( + IsochroneServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(IsochroneServiceAsyncClient), +) +def test_isochrone_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_CLIENT_CERTIFICATE is "Unsupported". + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + 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 cases for mTLS enablement when GOOGLE_API_USE_CLIENT_CERTIFICATE is unset. + test_cases = [ + ( + # With workloads present in config, mTLS is enabled. + { + "version": 1, + "cert_configs": { + "workload": { + "cert_path": "path/to/cert/file", + "key_path": "path/to/key/file", + } + }, + }, + mock_client_cert_source, + ), + ( + # With workloads not present in config, mTLS is disabled. + { + "version": 1, + "cert_configs": {}, + }, + None, + ), + ] + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + for config_data, expected_cert_source in test_cases: + env = os.environ.copy() + env.pop("GOOGLE_API_USE_CLIENT_CERTIFICATE", None) + with mock.patch.dict(os.environ, env, clear=True): + config_filename = "mock_certificate_config.json" + config_file_content = json.dumps(config_data) + m = mock.mock_open(read_data=config_file_content) + with mock.patch("builtins.open", m): + with mock.patch.dict( + os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} + ): + 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 expected_cert_source + + # Test cases for mTLS enablement when GOOGLE_API_USE_CLIENT_CERTIFICATE is unset(empty). + test_cases = [ + ( + # With workloads present in config, mTLS is enabled. + { + "version": 1, + "cert_configs": { + "workload": { + "cert_path": "path/to/cert/file", + "key_path": "path/to/key/file", + } + }, + }, + mock_client_cert_source, + ), + ( + # With workloads not present in config, mTLS is disabled. + { + "version": 1, + "cert_configs": {}, + }, + None, + ), + ] + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + for config_data, expected_cert_source in test_cases: + env = os.environ.copy() + env.pop("GOOGLE_API_USE_CLIENT_CERTIFICATE", "") + with mock.patch.dict(os.environ, env, clear=True): + config_filename = "mock_certificate_config.json" + config_file_content = json.dumps(config_data) + m = mock.mock_open(read_data=config_file_content) + with mock.patch("builtins.open", m): + with mock.patch.dict( + os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} + ): + 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 expected_cert_source + + # 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`" + ) + + +@pytest.mark.parametrize( + "client_class", [IsochroneServiceClient, IsochroneServiceAsyncClient] +) +@mock.patch.object( + IsochroneServiceClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(IsochroneServiceClient), +) +@mock.patch.object( + IsochroneServiceAsyncClient, + "_DEFAULT_ENDPOINT_TEMPLATE", + modify_default_endpoint_template(IsochroneServiceAsyncClient), +) +def test_isochrone_service_client_client_api_endpoint(client_class): + mock_client_cert_source = client_cert_source_callback + api_override = "foo.com" + default_universe = IsochroneServiceClient._DEFAULT_UNIVERSE + default_endpoint = IsochroneServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format( + UNIVERSE_DOMAIN=default_universe + ) + mock_universe = "bar.com" + mock_endpoint = IsochroneServiceClient._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", + [ + (IsochroneServiceClient, transports.IsochroneServiceGrpcTransport, "grpc"), + ( + IsochroneServiceAsyncClient, + transports.IsochroneServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + (IsochroneServiceClient, transports.IsochroneServiceRestTransport, "rest"), + ], +) +def test_isochrone_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", + [ + ( + IsochroneServiceClient, + transports.IsochroneServiceGrpcTransport, + "grpc", + grpc_helpers, + ), + ( + IsochroneServiceAsyncClient, + transports.IsochroneServiceGrpcAsyncIOTransport, + "grpc_asyncio", + grpc_helpers_async, + ), + ( + IsochroneServiceClient, + transports.IsochroneServiceRestTransport, + "rest", + None, + ), + ], +) +def test_isochrone_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_isochrone_service_client_client_options_from_dict(): + with mock.patch( + "google.maps.isochrones_v1.services.isochrone_service.transports.IsochroneServiceGrpcTransport.__init__" + ) as grpc_transport: + grpc_transport.return_value = None + client = IsochroneServiceClient( + 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", + [ + ( + IsochroneServiceClient, + transports.IsochroneServiceGrpcTransport, + "grpc", + grpc_helpers, + ), + ( + IsochroneServiceAsyncClient, + transports.IsochroneServiceGrpcAsyncIOTransport, + "grpc_asyncio", + grpc_helpers_async, + ), + ], +) +def test_isochrone_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( + "isochrones.googleapis.com:443", + credentials=file_creds, + credentials_file=None, + quota_project_id=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/maps-platform.isochrones", + ), + scopes=None, + default_host="isochrones.googleapis.com", + ssl_credentials=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + +@pytest.mark.parametrize( + "request_type", + [ + isochrones_service.GenerateIsochroneRequest(), + {}, + ], +) +def test_generate_isochrone(request_type, transport: str = "grpc"): + client = IsochroneServiceClient( + 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.generate_isochrone), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = isochrones_service.GenerateIsochroneResponse() + response = client.generate_isochrone(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + request = isochrones_service.GenerateIsochroneRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, isochrones_service.GenerateIsochroneResponse) + + +def test_generate_isochrone_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 = IsochroneServiceClient( + 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 = isochrones_service.GenerateIsochroneRequest( + place="place_value", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.generate_isochrone), "__call__" + ) as call: + call.return_value.name = ( + "foo" # operation_request.operation in compute client(s) expect a string. + ) + client.generate_isochrone(request=request) + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = isochrones_service.GenerateIsochroneRequest( + place="place_value", + ) + assert args[0] == request_msg + + +def test_generate_isochrone_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 = IsochroneServiceClient( + 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.generate_isochrone 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.generate_isochrone] = ( + mock_rpc + ) + request = {} + client.generate_isochrone(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.generate_isochrone(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_generate_isochrone_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 = IsochroneServiceAsyncClient( + 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.generate_isochrone + 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.generate_isochrone + ] = mock_rpc + + request = {} + await client.generate_isochrone(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + await client.generate_isochrone(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 +@pytest.mark.parametrize( + "request_type", + [ + isochrones_service.GenerateIsochroneRequest(), + {}, + ], +) +async def test_generate_isochrone_async(request_type, transport: str = "grpc_asyncio"): + client = IsochroneServiceAsyncClient( + 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.generate_isochrone), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + isochrones_service.GenerateIsochroneResponse() + ) + response = await client.generate_isochrone(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + request = isochrones_service.GenerateIsochroneRequest() + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, isochrones_service.GenerateIsochroneResponse) + + +def test_generate_isochrone_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 = IsochroneServiceClient( + 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.generate_isochrone 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.generate_isochrone] = ( + mock_rpc + ) + + request = {} + client.generate_isochrone(request) + + # Establish that the underlying gRPC stub method was called. + assert mock_rpc.call_count == 1 + + client.generate_isochrone(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_generate_isochrone_rest_required_fields( + request_type=isochrones_service.GenerateIsochroneRequest, +): + transport_class = transports.IsochroneServiceRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson(pb_request, use_integers_for_enums=False) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).generate_isochrone._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).generate_isochrone._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = IsochroneServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = isochrones_service.GenerateIsochroneResponse() + # 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 = isochrones_service.GenerateIsochroneResponse.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.generate_isochrone(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert sorted(expected_params) == sorted(actual_params) + + +def test_generate_isochrone_rest_unset_required_fields(): + transport = transports.IsochroneServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.generate_isochrone._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "travelDuration", + "travelMode", + "travelDirection", + ) + ) + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.IsochroneServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = IsochroneServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.IsochroneServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = IsochroneServiceClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.IsochroneServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = IsochroneServiceClient( + 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 = IsochroneServiceClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.IsochroneServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = IsochroneServiceClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.IsochroneServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = IsochroneServiceClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.IsochroneServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.IsochroneServiceGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.IsochroneServiceGrpcTransport, + transports.IsochroneServiceGrpcAsyncIOTransport, + transports.IsochroneServiceRestTransport, + ], +) +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 = IsochroneServiceClient.get_transport_class("grpc")( + credentials=ga_credentials.AnonymousCredentials() + ) + assert transport.kind == "grpc" + + +def test_initialize_client_w_grpc(): + client = IsochroneServiceClient( + 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_generate_isochrone_empty_call_grpc(): + client = IsochroneServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.generate_isochrone), "__call__" + ) as call: + call.return_value = isochrones_service.GenerateIsochroneResponse() + client.generate_isochrone(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = isochrones_service.GenerateIsochroneRequest() + assert args[0] == request_msg + + +def test_transport_kind_grpc_asyncio(): + transport = IsochroneServiceAsyncClient.get_transport_class("grpc_asyncio")( + credentials=async_anonymous_credentials() + ) + assert transport.kind == "grpc_asyncio" + + +def test_initialize_client_w_grpc_asyncio(): + client = IsochroneServiceAsyncClient( + 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_generate_isochrone_empty_call_grpc_asyncio(): + client = IsochroneServiceAsyncClient( + credentials=async_anonymous_credentials(), + transport="grpc_asyncio", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.generate_isochrone), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + isochrones_service.GenerateIsochroneResponse() + ) + await client.generate_isochrone(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = isochrones_service.GenerateIsochroneRequest() + assert args[0] == request_msg + + +def test_transport_kind_rest(): + transport = IsochroneServiceClient.get_transport_class("rest")( + credentials=ga_credentials.AnonymousCredentials() + ) + assert transport.kind == "rest" + + +def test_generate_isochrone_rest_bad_request( + request_type=isochrones_service.GenerateIsochroneRequest, +): + client = IsochroneServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + # send a request that will satisfy transcoding + request_init = {} + 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.generate_isochrone(request) + + +@pytest.mark.parametrize( + "request_type", + [ + isochrones_service.GenerateIsochroneRequest, + dict, + ], +) +def test_generate_isochrone_rest_call_success(request_type): + client = IsochroneServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + # send a request that will satisfy transcoding + request_init = {} + 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 = isochrones_service.GenerateIsochroneResponse() + + # 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 = isochrones_service.GenerateIsochroneResponse.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.generate_isochrone(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, isochrones_service.GenerateIsochroneResponse) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_generate_isochrone_rest_interceptors(null_interceptor): + transport = transports.IsochroneServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.IsochroneServiceRestInterceptor(), + ) + client = IsochroneServiceClient(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.IsochroneServiceRestInterceptor, "post_generate_isochrone" + ) as post, + mock.patch.object( + transports.IsochroneServiceRestInterceptor, + "post_generate_isochrone_with_metadata", + ) as post_with_metadata, + mock.patch.object( + transports.IsochroneServiceRestInterceptor, "pre_generate_isochrone" + ) as pre, + ): + pre.assert_not_called() + post.assert_not_called() + post_with_metadata.assert_not_called() + pb_message = isochrones_service.GenerateIsochroneRequest.pb( + isochrones_service.GenerateIsochroneRequest() + ) + 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 = isochrones_service.GenerateIsochroneResponse.to_json( + isochrones_service.GenerateIsochroneResponse() + ) + req.return_value.content = return_value + + request = isochrones_service.GenerateIsochroneRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = isochrones_service.GenerateIsochroneResponse() + post_with_metadata.return_value = ( + isochrones_service.GenerateIsochroneResponse(), + metadata, + ) + + client.generate_isochrone( + 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 = IsochroneServiceClient( + 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_generate_isochrone_empty_call_rest(): + client = IsochroneServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the actual call, and fake the request. + with mock.patch.object( + type(client.transport.generate_isochrone), "__call__" + ) as call: + client.generate_isochrone(request=None) + + # Establish that the underlying stub method was called. + call.assert_called() + _, args, _ = call.mock_calls[0] + request_msg = isochrones_service.GenerateIsochroneRequest() + assert args[0] == request_msg + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = IsochroneServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.IsochroneServiceGrpcTransport, + ) + + +def test_isochrone_service_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.IsochroneServiceTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_isochrone_service_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.maps.isochrones_v1.services.isochrone_service.transports.IsochroneServiceTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.IsochroneServiceTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ("generate_isochrone",) + 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_isochrone_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.maps.isochrones_v1.services.isochrone_service.transports.IsochroneServiceTransport._prep_wrapped_messages" + ) as Transport, + ): + Transport.return_value = None + load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.IsochroneServiceTransport( + 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/cloud-platform", + "https://www.googleapis.com/auth/maps-platform.isochrones", + ), + quota_project_id="octopus", + ) + + +def test_isochrone_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.maps.isochrones_v1.services.isochrone_service.transports.IsochroneServiceTransport._prep_wrapped_messages" + ) as Transport, + ): + Transport.return_value = None + adc.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.IsochroneServiceTransport() + adc.assert_called_once() + + +def test_isochrone_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) + IsochroneServiceClient() + adc.assert_called_once_with( + scopes=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/maps-platform.isochrones", + ), + quota_project_id=None, + ) + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.IsochroneServiceGrpcTransport, + transports.IsochroneServiceGrpcAsyncIOTransport, + ], +) +def test_isochrone_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/cloud-platform", + "https://www.googleapis.com/auth/maps-platform.isochrones", + ), + quota_project_id="octopus", + ) + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.IsochroneServiceGrpcTransport, + transports.IsochroneServiceGrpcAsyncIOTransport, + transports.IsochroneServiceRestTransport, + ], +) +def test_isochrone_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.IsochroneServiceGrpcTransport, grpc_helpers), + (transports.IsochroneServiceGrpcAsyncIOTransport, grpc_helpers_async), + ], +) +def test_isochrone_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( + "isochrones.googleapis.com:443", + credentials=creds, + credentials_file=None, + quota_project_id="octopus", + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/maps-platform.isochrones", + ), + scopes=["1", "2"], + default_host="isochrones.googleapis.com", + ssl_credentials=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.IsochroneServiceGrpcTransport, + transports.IsochroneServiceGrpcAsyncIOTransport, + ], +) +def test_isochrone_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_isochrone_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.IsochroneServiceRestTransport( + 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_isochrone_service_host_no_port(transport_name): + client = IsochroneServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions( + api_endpoint="isochrones.googleapis.com" + ), + transport=transport_name, + ) + assert client.transport._host == ( + "isochrones.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://isochrones.googleapis.com" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "grpc_asyncio", + "rest", + ], +) +def test_isochrone_service_host_with_port(transport_name): + client = IsochroneServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions( + api_endpoint="isochrones.googleapis.com:8000" + ), + transport=transport_name, + ) + assert client.transport._host == ( + "isochrones.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://isochrones.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_isochrone_service_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = IsochroneServiceClient( + credentials=creds1, + transport=transport_name, + ) + client2 = IsochroneServiceClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.generate_isochrone._session + session2 = client2.transport.generate_isochrone._session + assert session1 != session2 + + +def test_isochrone_service_grpc_transport_channel(): + channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials()) + + # Check that channel is used if provided. + transport = transports.IsochroneServiceGrpcTransport( + 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_isochrone_service_grpc_asyncio_transport_channel(): + channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials()) + + # Check that channel is used if provided. + transport = transports.IsochroneServiceGrpcAsyncIOTransport( + 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.filterwarnings("ignore::FutureWarning") +@pytest.mark.parametrize( + "transport_class", + [ + transports.IsochroneServiceGrpcTransport, + transports.IsochroneServiceGrpcAsyncIOTransport, + ], +) +def test_isochrone_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.IsochroneServiceGrpcTransport, + transports.IsochroneServiceGrpcAsyncIOTransport, + ], +) +def test_isochrone_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_place_path(): + place_id = "squid" + expected = "places/{place_id}".format( + place_id=place_id, + ) + actual = IsochroneServiceClient.place_path(place_id) + assert expected == actual + + +def test_parse_place_path(): + expected = { + "place_id": "clam", + } + path = IsochroneServiceClient.place_path(**expected) + + # Check that the path construction is reversible. + actual = IsochroneServiceClient.parse_place_path(path) + assert expected == actual + + +def test_common_billing_account_path(): + billing_account = "whelk" + expected = "billingAccounts/{billing_account}".format( + billing_account=billing_account, + ) + actual = IsochroneServiceClient.common_billing_account_path(billing_account) + assert expected == actual + + +def test_parse_common_billing_account_path(): + expected = { + "billing_account": "octopus", + } + path = IsochroneServiceClient.common_billing_account_path(**expected) + + # Check that the path construction is reversible. + actual = IsochroneServiceClient.parse_common_billing_account_path(path) + assert expected == actual + + +def test_common_folder_path(): + folder = "oyster" + expected = "folders/{folder}".format( + folder=folder, + ) + actual = IsochroneServiceClient.common_folder_path(folder) + assert expected == actual + + +def test_parse_common_folder_path(): + expected = { + "folder": "nudibranch", + } + path = IsochroneServiceClient.common_folder_path(**expected) + + # Check that the path construction is reversible. + actual = IsochroneServiceClient.parse_common_folder_path(path) + assert expected == actual + + +def test_common_organization_path(): + organization = "cuttlefish" + expected = "organizations/{organization}".format( + organization=organization, + ) + actual = IsochroneServiceClient.common_organization_path(organization) + assert expected == actual + + +def test_parse_common_organization_path(): + expected = { + "organization": "mussel", + } + path = IsochroneServiceClient.common_organization_path(**expected) + + # Check that the path construction is reversible. + actual = IsochroneServiceClient.parse_common_organization_path(path) + assert expected == actual + + +def test_common_project_path(): + project = "winkle" + expected = "projects/{project}".format( + project=project, + ) + actual = IsochroneServiceClient.common_project_path(project) + assert expected == actual + + +def test_parse_common_project_path(): + expected = { + "project": "nautilus", + } + path = IsochroneServiceClient.common_project_path(**expected) + + # Check that the path construction is reversible. + actual = IsochroneServiceClient.parse_common_project_path(path) + assert expected == actual + + +def test_common_location_path(): + project = "scallop" + location = "abalone" + expected = "projects/{project}/locations/{location}".format( + project=project, + location=location, + ) + actual = IsochroneServiceClient.common_location_path(project, location) + assert expected == actual + + +def test_parse_common_location_path(): + expected = { + "project": "squid", + "location": "clam", + } + path = IsochroneServiceClient.common_location_path(**expected) + + # Check that the path construction is reversible. + actual = IsochroneServiceClient.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.IsochroneServiceTransport, "_prep_wrapped_messages" + ) as prep: + client = IsochroneServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + with mock.patch.object( + transports.IsochroneServiceTransport, "_prep_wrapped_messages" + ) as prep: + transport_class = IsochroneServiceClient.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 = IsochroneServiceClient( + 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 = IsochroneServiceAsyncClient( + 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 = IsochroneServiceClient( + 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 = IsochroneServiceClient( + 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", + [ + (IsochroneServiceClient, transports.IsochroneServiceGrpcTransport), + (IsochroneServiceAsyncClient, transports.IsochroneServiceGrpcAsyncIOTransport), + ], +) +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-resumable-media/mypy.ini b/packages/google-resumable-media/mypy.ini deleted file mode 100644 index 4505b485436b..000000000000 --- a/packages/google-resumable-media/mypy.ini +++ /dev/null @@ -1,3 +0,0 @@ -[mypy] -python_version = 3.6 -namespace_packages = True diff --git a/packages/google-resumable-media/noxfile.py b/packages/google-resumable-media/noxfile.py index a1c1ae199709..6d0f35f30375 100644 --- a/packages/google-resumable-media/noxfile.py +++ b/packages/google-resumable-media/noxfile.py @@ -21,6 +21,17 @@ import nox CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) + SYSTEM_TEST_ENV_VARS = ("GOOGLE_APPLICATION_CREDENTIALS",) RUFF_VERSION = "ruff==0.14.14" @@ -68,7 +79,7 @@ def unit(session): line_coverage, os.path.join("tests", "unit"), os.path.join("tests_async", "unit"), - *session.posargs + *session.posargs, ) @@ -106,6 +117,7 @@ def docs(session): os.path.join("docs", "_build", "html", ""), ) + @nox.session(python="3.10") def docfx(session): """Build the docfx yaml files for this library.""" @@ -251,7 +263,16 @@ def mypy(session): "types-requests", "types-mock", ) - session.run("mypy", "-p", "google", "-p", "tests", "-p", "tests_async") + session.run( + "mypy", + f"--config-file={MYPY_CONFIG_FILE}", + "-p", + "google", + "-p", + "tests", + "-p", + "tests_async", + ) @nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS) @@ -319,7 +340,9 @@ def prerelease_deps(session): # version, the first version we test with in the unit tests sessions has a # constraints file containing all dependencies and extras. with open( - CURRENT_DIRECTORY / "testing" / f"constraints-{UNIT_TEST_PYTHON_VERSIONS[0]}.txt", + CURRENT_DIRECTORY + / "testing" + / f"constraints-{UNIT_TEST_PYTHON_VERSIONS[0]}.txt", encoding="utf-8", ) as constraints_file: constraints_text = constraints_file.read() @@ -400,7 +423,9 @@ def core_deps_from_source(session): # version, the first version we test with in the unit tests sessions has a # constraints file containing all dependencies and extras. with open( - CURRENT_DIRECTORY / "testing" / f"constraints-{UNIT_TEST_PYTHON_VERSIONS[0]}.txt", + CURRENT_DIRECTORY + / "testing" + / f"constraints-{UNIT_TEST_PYTHON_VERSIONS[0]}.txt", encoding="utf-8", ) as constraints_file: constraints_text = constraints_file.read() @@ -431,7 +456,9 @@ def core_deps_from_source(session): dep_paths = [str(deps_dir / dep) for dep in core_dependencies_from_source] session.install(*dep_paths, "--no-deps", "--ignore-installed") - print(f"Installed {', '.join(core_dependencies_from_source)} locally from {deps_dir}") + print( + f"Installed {', '.join(core_dependencies_from_source)} locally from {deps_dir}" + ) other_deps = [ "cryptography", diff --git a/packages/pandas-gbq/noxfile.py b/packages/pandas-gbq/noxfile.py index 8e58d09628bc..aabc6426becb 100644 --- a/packages/pandas-gbq/noxfile.py +++ b/packages/pandas-gbq/noxfile.py @@ -79,6 +79,17 @@ SYSTEM_TEST_EXTRAS_BY_PYTHON = {} CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) + # Error if a python version is missing nox.options.error_on_missing_interpreters = True @@ -544,6 +555,7 @@ def mypy(session): session.install(".") session.run( "mypy", + f"--config-file={MYPY_CONFIG_FILE}", "pandas_gbq", "--check-untyped-defs", *session.posargs, diff --git a/packages/proto-plus/CHANGELOG.md b/packages/proto-plus/CHANGELOG.md index c4e147c82eaf..86f2fc5266cd 100644 --- a/packages/proto-plus/CHANGELOG.md +++ b/packages/proto-plus/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/proto-plus/#history +## [1.28.2](https://github.com/googleapis/google-cloud-python/compare/proto-plus-v1.28.1...proto-plus-v1.28.2) (2026-07-22) + + +### Bug Fixes + +* **proto-plus:** make Marshal thread-safe and handle race conditions ([#17774](https://github.com/googleapis/google-cloud-python/issues/17774)) ([0719f1e](https://github.com/googleapis/google-cloud-python/commit/0719f1ec1d4146dbfc96d24c95b7ccafd31ab447)), closes [#15100](https://github.com/googleapis/google-cloud-python/issues/15100) + ## [1.28.1](https://github.com/googleapis/google-cloud-python/compare/proto-plus-v1.28.0...proto-plus-v1.28.1) (2026-07-07) diff --git a/packages/proto-plus/noxfile.py b/packages/proto-plus/noxfile.py index 4e07083cd49c..047e6d9b2221 100644 --- a/packages/proto-plus/noxfile.py +++ b/packages/proto-plus/noxfile.py @@ -27,6 +27,17 @@ LINT_PATHS = ["docs", "proto", "tests", "noxfile.py", "setup.py"] CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# Path to the centralized mypy configuration file at the repository root. +# Search upwards to support running nox from both monorepo packages and integration test goldens. +MYPY_CONFIG_FILE = next( + ( + str(p / "mypy.ini") + for p in CURRENT_DIRECTORY.parents + if (p / "mypy.ini").exists() + ), + str(CURRENT_DIRECTORY.parent.parent / "mypy.ini"), +) + DEFAULT_PYTHON_VERSION = "3.14" @@ -307,6 +318,21 @@ def mypy(session): # Enable mypy once this bug is fixed. session.skip("Skip mypy since this library doesn't have py.typed") + session.install("-e", ".") + session.install( + "mypy", + "types-setuptools", + "types-protobuf", + "types-requests", + ) + session.run( + "mypy", + f"--config-file={MYPY_CONFIG_FILE}", + "-p", + "proto", + *session.posargs, + ) + @nox.session(python=DEFAULT_PYTHON_VERSION) def lint(session): diff --git a/packages/proto-plus/proto/version.py b/packages/proto-plus/proto/version.py index 20c5f62e98de..3c3485fcc1f6 100644 --- a/packages/proto-plus/proto/version.py +++ b/packages/proto-plus/proto/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.28.1" +__version__ = "1.28.2" diff --git a/packages/sqlalchemy-bigquery/.coveragerc b/packages/sqlalchemy-bigquery/.coveragerc index 73bf56dd1535..7b0d6b3cd7dc 100644 --- a/packages/sqlalchemy-bigquery/.coveragerc +++ b/packages/sqlalchemy-bigquery/.coveragerc @@ -22,7 +22,7 @@ omit = sqlalchemy_bigquery/requirements.py [report] -fail_under = 98 +fail_under = 100 show_missing = True exclude_lines = # Re-enable the standard pragma @@ -37,3 +37,5 @@ omit = */core/*.py */site-packages/*.py sqlalchemy_bigquery/requirements.py + tests/* + */tests/* diff --git a/packages/sqlalchemy-bigquery/CHANGELOG.md b/packages/sqlalchemy-bigquery/CHANGELOG.md index 96aa23c11357..2f57c517e4cf 100644 --- a/packages/sqlalchemy-bigquery/CHANGELOG.md +++ b/packages/sqlalchemy-bigquery/CHANGELOG.md @@ -8,6 +8,15 @@ Older versions of this project were distributed as [pybigquery][0]. [1]: https://pypi.org/project/sqlalchemy-bigquery/#history +## [1.17.1](https://github.com/googleapis/google-cloud-python/compare/sqlalchemy-bigquery-v1.17.0...sqlalchemy-bigquery-v1.17.1) (2026-07-22) + + +### Bug Fixes + +* sqlalchemy bigquery python 3.15 compat ([#17788](https://github.com/googleapis/google-cloud-python/issues/17788)) ([14447fb](https://github.com/googleapis/google-cloud-python/commit/14447fb9edcd4b7e0680d82399af289b1145d6ed)), closes [#17786](https://github.com/googleapis/google-cloud-python/issues/17786) +* **sqlalchemy-bigquery:** update literal binds test for SQLAlchemy 2.0 ([#17029](https://github.com/googleapis/google-cloud-python/issues/17029)) ([74f7a41](https://github.com/googleapis/google-cloud-python/commit/74f7a41fc957f4b8e1de91b193c756b6a99637e4)) +* **sqlalchemy-bigquery:** wrap string in WKT in geography system tests ([#17780](https://github.com/googleapis/google-cloud-python/issues/17780)) ([26d43c1](https://github.com/googleapis/google-cloud-python/commit/26d43c1fd820e058dd016084be2bdb459c288aff)) + ## [1.17.0](https://github.com/googleapis/google-cloud-python/compare/sqlalchemy-bigquery-v1.16.0...sqlalchemy-bigquery-v1.17.0) (2026-05-06) diff --git a/packages/sqlalchemy-bigquery/sqlalchemy_bigquery/geography.py b/packages/sqlalchemy-bigquery/sqlalchemy_bigquery/geography.py index 670fc576b2e2..744bfc803c70 100644 --- a/packages/sqlalchemy-bigquery/sqlalchemy_bigquery/geography.py +++ b/packages/sqlalchemy-bigquery/sqlalchemy_bigquery/geography.py @@ -183,9 +183,8 @@ def _fixup_st_arguments(element, compiler, **kw): argument_types = _argument_types.get(element.name.lower()) if argument_types: for argument_type, argument in zip(argument_types, element.clauses.clauses): - if isinstance(argument, BindParameter) and ( - argument.type is not argument_type - or not isinstance(argument.type, argument_type) + if isinstance(argument, BindParameter) and not isinstance( + argument.type, argument_type ): argument.type = argument_type() diff --git a/packages/sqlalchemy-bigquery/sqlalchemy_bigquery/version.py b/packages/sqlalchemy-bigquery/sqlalchemy_bigquery/version.py index 38787d1b6664..69ff04758b47 100644 --- a/packages/sqlalchemy-bigquery/sqlalchemy_bigquery/version.py +++ b/packages/sqlalchemy-bigquery/sqlalchemy_bigquery/version.py @@ -17,4 +17,4 @@ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -__version__ = "1.17.0" +__version__ = "1.17.1" diff --git a/packages/sqlalchemy-bigquery/tests/unit/test_engine.py b/packages/sqlalchemy-bigquery/tests/unit/test_engine.py index 67265b5ab853..7362ab588425 100644 --- a/packages/sqlalchemy-bigquery/tests/unit/test_engine.py +++ b/packages/sqlalchemy-bigquery/tests/unit/test_engine.py @@ -70,3 +70,9 @@ def test_arraysize_querystring_takes_precedence_over_default(faux_conn, metadata metadata.create_all(engine) assert conn.connection.test_data["arraysize"] == arraysize + + +def test_dbapi_deprecated(): + from sqlalchemy_bigquery.base import BigQueryDialect + + assert BigQueryDialect.dbapi() is BigQueryDialect.import_dbapi() diff --git a/packages/sqlalchemy-bigquery/tests/unit/test_geography.py b/packages/sqlalchemy-bigquery/tests/unit/test_geography.py index d03dbf940d93..332cb1c3a954 100644 --- a/packages/sqlalchemy-bigquery/tests/unit/test_geography.py +++ b/packages/sqlalchemy-bigquery/tests/unit/test_geography.py @@ -24,8 +24,6 @@ geoalchemy2 = pytest.importorskip("geoalchemy2") -# TODO(http://github.com/googleapis/google-cloud-python/issues/17287): Unskip once bug is resolved. -@pytest.mark.skip(reason="Failing in CI with AssertionError.") def test_geoalchemy2_core(faux_conn, last_query): """Make sure GeoAlchemy 2 Core Tutorial works as adapted to only having geometry""" conn = faux_conn @@ -34,7 +32,7 @@ def test_geoalchemy2_core(faux_conn, last_query): from sqlalchemy import Column, String - from sqlalchemy_bigquery import GEOGRAPHY + from sqlalchemy_bigquery import GEOGRAPHY, WKT lake_table = setup_table( conn, "lake", Column("name", String), Column("geog", GEOGRAPHY) @@ -83,7 +81,7 @@ def test_geoalchemy2_core(faux_conn, last_query): except Exception: pass # sqlite had no special functions :) last_query( - "SELECT `lake`.`name`, ST_AsBinary(`lake`.`geog`) AS `geog` \n" "FROM `lake`" + "SELECT `lake`.`name`, ST_AsBinary(`lake`.`geog`) AS `geog` \nFROM `lake`" ) # Spatial query @@ -93,7 +91,7 @@ def test_geoalchemy2_core(faux_conn, last_query): try: conn.execute( select(lake_table.c.name).where( - func.ST_Contains(lake_table.c.geog, "POINT(4 1)") + func.ST_Contains(lake_table.c.geog, WKT("POINT(4 1)")) ) ) except Exception: @@ -101,8 +99,8 @@ def test_geoalchemy2_core(faux_conn, last_query): last_query( "SELECT `lake`.`name` \n" "FROM `lake` \n" - "WHERE ST_Contains(`lake`.`geog`, %(ST_Contains_1:geography)s)", - {"ST_Contains_1": "POINT(4 1)"}, + "WHERE ST_Contains(`lake`.`geog`, ST_GeogFromText(%(ST_GeogFromText_1:STRING)s))", + {"ST_GeogFromText_1": "POINT(4 1)"}, ) try: @@ -183,3 +181,31 @@ def test_calling_st_functions_that_dont_take_geographies(faux_conn, last_query): " AS `ST_GeogFromText_1`", dict(ST_GeogFromText_2="point(0 0)"), ) + + +def test_fixup_st_arguments(): + from geoalchemy2.functions import GenericFunction, ST_Area + from sqlalchemy.sql.elements import BindParameter + + from sqlalchemy_bigquery.geography import GEOGRAPHY, _fixup_st_arguments + + class DummyCompiler: + def visit_function(self, element, **kw): + return "func(param)" + + # Case 1: argument.type is not yet GEOGRAPHY + func_element = ST_Area(BindParameter("param", "point(0 0)")) + res = _fixup_st_arguments(func_element, DummyCompiler()) + assert res == "func(param)" + assert isinstance(func_element.clauses.clauses[0].type, GEOGRAPHY) + + # Case 2: argument.type is ALREADY GEOGRAPHY + func_element2 = ST_Area(BindParameter("param", "point(0 0)", type_=GEOGRAPHY())) + _fixup_st_arguments(func_element2, DummyCompiler()) + + # Case 3: function without specified argument types + class ST_Unknown(GenericFunction): + name = "ST_Unknown" + + func_element3 = ST_Unknown(BindParameter("param", "point(0 0)")) + _fixup_st_arguments(func_element3, DummyCompiler()) diff --git a/release-please-bulk-config.json b/release-please-bulk-config.json index 8fb2fed29669..57ebae116c4d 100644 --- a/release-please-bulk-config.json +++ b/release-please-bulk-config.json @@ -1019,18 +1019,6 @@ } ] }, - "packages/google-cloud-commerceproducer": { - "component": "google-cloud-commerceproducer", - "extra-files": [ - "google/cloud/commerceproducer/gapic_version.py", - "google/cloud/commerceproducer_v1beta/gapic_version.py", - { - "jsonpath": "$.clientLibrary.version", - "path": "samples/generated_samples/snippet_metadata_google.cloud.commerceproducer.v1beta.json", - "type": "json" - } - ] - }, "packages/google-cloud-common": { "component": "google-cloud-common", "extra-files": [ @@ -3896,6 +3884,9 @@ "packages/proto-plus": { "component": "proto-plus" }, + "packages/sqlalchemy-bigquery": { + "component": "sqlalchemy-bigquery" + }, "packages/sqlalchemy-spanner": { "component": "sqlalchemy-spanner" } diff --git a/release-please-individual-config.json b/release-please-individual-config.json index f9abdc852d2a..c1dec077c934 100644 --- a/release-please-individual-config.json +++ b/release-please-individual-config.json @@ -5,14 +5,47 @@ "packages/bigframes": { "component": "bigframes" }, + "packages/google-cloud-commerceproducer": { + "component": "google-cloud-commerceproducer", + "extra-files": [ + "google/cloud/commerceproducer/gapic_version.py", + "google/cloud/commerceproducer_v1beta/gapic_version.py", + { + "jsonpath": "$.clientLibrary.version", + "path": "samples/generated_samples/snippet_metadata_google.cloud.commerceproducer.v1beta.json", + "type": "json" + } + ] + }, + "packages/google-cloud-productregistry": { + "component": "google-cloud-productregistry", + "extra-files": [ + "google/cloud/productregistry/gapic_version.py", + "google/cloud/productregistry_v1/gapic_version.py", + { + "jsonpath": "$.clientLibrary.version", + "path": "samples/generated_samples/snippet_metadata_google.cloud.productregistry.v1.json", + "type": "json" + } + ] + }, "packages/google-crc32c": { "component": "google-crc32c" }, + "packages/google-maps-isochrones": { + "component": "google-maps-isochrones", + "extra-files": [ + "google/maps/isochrones/gapic_version.py", + "google/maps/isochrones_v1/gapic_version.py", + { + "jsonpath": "$.clientLibrary.version", + "path": "samples/generated_samples/snippet_metadata_google.maps.isochrones.v1.json", + "type": "json" + } + ] + }, "packages/pandas-gbq": { "component": "pandas-gbq" - }, - "packages/sqlalchemy-bigquery": { - "component": "sqlalchemy-bigquery" } }, "release-type": "python-librarian",