From 5cde4b745e1766dcd69192bbcb296c38ade4af97 Mon Sep 17 00:00:00 2001 From: Anna Cocuzzo <63511057+acocuzzo@users.noreply.github.com> Date: Sat, 1 Apr 2023 09:38:02 -0400 Subject: [PATCH 1/7] samples: cleanup test resources (#893) --- samples/snippets/schema_test.py | 248 +++++++++++++++++++++------- samples/snippets/subscriber_test.py | 2 + 2 files changed, 194 insertions(+), 56 deletions(-) diff --git a/samples/snippets/schema_test.py b/samples/snippets/schema_test.py index f62449e88..ccf65034f 100644 --- a/samples/snippets/schema_test.py +++ b/samples/snippets/schema_test.py @@ -23,7 +23,7 @@ from google.api_core.exceptions import InternalServerError, NotFound from google.cloud import pubsub_v1 from google.cloud.pubsub import PublisherClient, SchemaServiceClient, SubscriberClient -from google.pubsub_v1.types import Encoding +from google.pubsub_v1.types import Encoding, Schema, Topic import pytest import schema @@ -35,18 +35,26 @@ except KeyError: raise KeyError("Need to set GOOGLE_CLOUD_PROJECT as an environment variable.") AVRO_TOPIC_ID = f"schema-test-avro-topic-{UUID}" +AVRO_TOPIC_ID_TO_CREATE = f"schema-test-avro-topic-to-create-{UUID}" PROTO_TOPIC_ID = f"schema-test-proto-topic-{UUID}" PROTO_WITH_REVISIONS_TOPIC_ID = f"schema-test-proto-with-revisions-topic-{UUID}" +PROTO_WITH_REVISIONS_TOPIC_ID_TO_CREATE = ( + f"schema-test-proto-with-revisions-topic-to-create-{UUID}" +) AVRO_SUBSCRIPTION_ID = f"schema-test-avro-subscription-{UUID}" PROTO_SUBSCRIPTION_ID = f"schema-test-proto-subscription-{UUID}" AVRO_SCHEMA_ID = f"schema-test-avro-schema-{UUID}" +AVRO_SCHEMA_ID_TO_CREATE = f"schema-test-avro-schema-to-create-{UUID}" PROTO_SCHEMA_ID = f"schema-test-proto-schema-{UUID}" +PROTO_SCHEMA_ID_TO_CREATE = f"schema-test-proto-schema-to-create-{UUID}" +PROTO_SCHEMA_ID_TO_DELETE = f"schema-test-proto-schema-to-delete-{UUID}" AVSC_FILE = "resources/us-states.avsc" AVSC_REVISION_FILE = "resources/us-states.avsc" PROTO_FILE = "resources/us-states.proto" PROTO_REVISION_FILE = "resources/us-states.proto" -# These tests run in parallel if pytest-parallel is installed. +# These tests run in parallel in continuous integration, +# with the same UUID. # Avoid modifying resources that are shared across tests, # as this results in test flake. @@ -57,11 +65,42 @@ def schema_client() -> Generator[pubsub_v1.SchemaServiceClient, None, None]: yield schema_client +def ensure_schema_exists( + name: str, type: Schema.Type, schema_client: pubsub_v1.SchemaServiceClient +) -> Schema: + schema_path = schema_client.schema_path(PROJECT_ID, name) + + try: + return schema_client.get_schema(request={"name": schema_path}) + except NotFound: + project_path = f"projects/{PROJECT_ID}" + with open(AVSC_FILE if type == Schema.Type.AVRO else PROTO_FILE, "rb") as f: + definition_text = f.read().decode("utf-8") + schema = Schema(name=schema_path, type_=type, definition=definition_text) + return schema_client.create_schema( + request={"parent": project_path, "schema": schema, "schema_id": name} + ) + + @pytest.fixture(scope="module") def avro_schema( schema_client: pubsub_v1.SchemaServiceClient, ) -> Generator[str, None, None]: - avro_schema_path = schema_client.schema_path(PROJECT_ID, AVRO_SCHEMA_ID) + avro_schema = ensure_schema_exists(AVRO_SCHEMA_ID, Schema.Type.AVRO, schema_client) + + yield avro_schema.name + + try: + schema_client.delete_schema(request={"name": avro_schema.name}) + except (NotFound, InternalServerError): + pass + + +@pytest.fixture(scope="module") +def avro_schema_to_create( + schema_client: pubsub_v1.SchemaServiceClient, +) -> Generator[str, None, None]: + avro_schema_path = schema_client.schema_path(PROJECT_ID, AVRO_SCHEMA_ID_TO_CREATE) yield avro_schema_path @@ -75,7 +114,39 @@ def avro_schema( def proto_schema( schema_client: pubsub_v1.SchemaServiceClient, ) -> Generator[str, None, None]: - proto_schema_path = schema_client.schema_path(PROJECT_ID, PROTO_SCHEMA_ID) + proto_schema = ensure_schema_exists( + PROTO_SCHEMA_ID, Schema.Type.PROTOCOL_BUFFER, schema_client + ) + + yield proto_schema.name + + try: + schema_client.delete_schema(request={"name": proto_schema.name}) + except (NotFound, InternalServerError): + pass + + +@pytest.fixture(scope="module") +def proto_schema_to_delete( + schema_client: pubsub_v1.SchemaServiceClient, +) -> Generator[str, None, None]: + proto_schema = ensure_schema_exists( + PROTO_SCHEMA_ID_TO_DELETE, Schema.Type.PROTOCOL_BUFFER, schema_client + ) + + yield proto_schema.name + + try: + schema_client.delete_schema(request={"name": proto_schema.name}) + except (NotFound, InternalServerError): + pass + + +@pytest.fixture(scope="module") +def proto_schema_to_create( + schema_client: pubsub_v1.SchemaServiceClient, +) -> Generator[str, None, None]: + proto_schema_path = schema_client.schema_path(PROJECT_ID, PROTO_SCHEMA_ID_TO_CREATE) yield proto_schema_path @@ -90,54 +161,99 @@ def publisher_client() -> Generator[pubsub_v1.PublisherClient, None, None]: yield PublisherClient() -@pytest.fixture(scope="module") -def avro_topic( - publisher_client: pubsub_v1.PublisherClient, avro_schema: str -) -> Generator[str, None, None]: - from google.pubsub_v1.types import Encoding - - avro_topic_path = publisher_client.topic_path(PROJECT_ID, AVRO_TOPIC_ID) +def ensure_topic_exists( + name: str, + schema_path: str, + encoding: Encoding, + publisher_client: pubsub_v1.PublisherClient, +) -> Topic: + topic_path = publisher_client.topic_path(PROJECT_ID, name) try: - avro_topic = publisher_client.get_topic(request={"topic": avro_topic_path}) + return publisher_client.get_topic(request={"topic": topic_path}) except NotFound: - avro_topic = publisher_client.create_topic( + return publisher_client.create_topic( request={ - "name": avro_topic_path, + "name": topic_path, "schema_settings": { - "schema": avro_schema, - "encoding": Encoding.BINARY, + "schema": schema_path, + "encoding": encoding, }, } ) + +@pytest.fixture(scope="module") +def avro_topic( + publisher_client: pubsub_v1.PublisherClient, avro_schema: str +) -> Generator[str, None, None]: + avro_topic = ensure_topic_exists( + AVRO_TOPIC_ID, avro_schema, Encoding.BINARY, publisher_client + ) + yield avro_topic.name + try: + publisher_client.delete_topic(request={"topic": avro_topic.name}) + except NotFound: + pass - publisher_client.delete_topic(request={"topic": avro_topic.name}) + +@pytest.fixture(scope="module") +def avro_topic_to_create( + publisher_client: pubsub_v1.PublisherClient, avro_schema: str +) -> Generator[str, None, None]: + avro_topic_path = publisher_client.topic_path(PROJECT_ID, AVRO_TOPIC_ID_TO_CREATE) + + yield avro_topic_path + try: + publisher_client.delete_topic(request={"topic": avro_topic_path}) + except NotFound: + pass @pytest.fixture(scope="module") def proto_topic( publisher_client: pubsub_v1.PublisherClient, proto_schema: str ) -> Generator[str, None, None]: - proto_topic_path = publisher_client.topic_path(PROJECT_ID, PROTO_TOPIC_ID) + proto_topic = ensure_topic_exists( + PROTO_TOPIC_ID, proto_schema, Encoding.BINARY, publisher_client + ) + yield proto_topic.name try: - proto_topic = publisher_client.get_topic(request={"topic": proto_topic_path}) + publisher_client.delete_topic(request={"topic": proto_topic.name}) except NotFound: - proto_topic = publisher_client.create_topic( - request={ - "name": proto_topic_path, - "schema_settings": { - "schema": proto_schema, - "encoding": Encoding.BINARY, - }, - } - ) + pass + + +@pytest.fixture(scope="module") +def proto_with_revisions_topic( + publisher_client: pubsub_v1.PublisherClient, proto_schema: str +) -> Generator[str, None, None]: + proto_topic = ensure_topic_exists( + PROTO_WITH_REVISIONS_TOPIC_ID, proto_schema, Encoding.BINARY, publisher_client + ) yield proto_topic.name + try: + publisher_client.delete_topic(request={"topic": proto_topic.name}) + except NotFound: + pass + + +@pytest.fixture(scope="module") +def proto_with_revisions_topic_to_create( + publisher_client: pubsub_v1.PublisherClient, proto_schema: str +) -> Generator[str, None, None]: + topic_path = publisher_client.topic_path( + PROJECT_ID, PROTO_WITH_REVISIONS_TOPIC_ID_TO_CREATE + ) - publisher_client.delete_topic(request={"topic": proto_topic.name}) + yield topic_path + try: + publisher_client.delete_topic(request={"topic": topic_path}) + except NotFound: + pass @pytest.fixture(scope="module") @@ -166,9 +282,12 @@ def avro_subscription( yield avro_subscription.name - subscriber_client.delete_subscription( - request={"subscription": avro_subscription.name} - ) + try: + subscriber_client.delete_subscription( + request={"subscription": avro_subscription.name} + ) + except NotFound: + pass @pytest.fixture(scope="module") @@ -190,43 +309,46 @@ def proto_subscription( yield proto_subscription.name - subscriber_client.delete_subscription( - request={"subscription": proto_subscription.name} - ) + try: + subscriber_client.delete_subscription( + request={"subscription": proto_subscription.name} + ) + except NotFound: + pass def test_create_avro_schema( schema_client: pubsub_v1.SchemaServiceClient, - avro_schema: str, + avro_schema_to_create: str, capsys: CaptureFixture[str], ) -> None: try: - schema_client.delete_schema(request={"name": avro_schema}) + schema_client.delete_schema(request={"name": avro_schema_to_create}) except NotFound: pass - schema.create_avro_schema(PROJECT_ID, AVRO_SCHEMA_ID, AVSC_FILE) + schema.create_avro_schema(PROJECT_ID, AVRO_SCHEMA_ID_TO_CREATE, AVSC_FILE) out, _ = capsys.readouterr() assert "Created a schema using an Avro schema file:" in out - assert f"{avro_schema}" in out + assert f"{avro_schema_to_create}" in out def test_create_proto_schema( schema_client: pubsub_v1.SchemaServiceClient, - proto_schema: str, + proto_schema_to_create: str, capsys: CaptureFixture[str], ) -> None: try: - schema_client.delete_schema(request={"name": proto_schema}) + schema_client.delete_schema(request={"name": proto_schema_to_create}) except NotFound: pass - schema.create_proto_schema(PROJECT_ID, PROTO_SCHEMA_ID, PROTO_FILE) + schema.create_proto_schema(PROJECT_ID, PROTO_SCHEMA_ID_TO_CREATE, PROTO_FILE) out, _ = capsys.readouterr() assert "Created a schema using a protobuf schema file:" in out - assert f"{proto_schema}" in out + assert f"{proto_schema_to_create}" in out def test_commit_avro_schema( @@ -260,7 +382,7 @@ def test_get_schema(avro_schema: str, capsys: CaptureFixture[str]) -> None: assert f"{avro_schema}" in out -def test_get_schema_revsion(avro_schema: str, capsys: CaptureFixture[str]) -> None: +def test_get_schema_revision(avro_schema: str, capsys: CaptureFixture[str]) -> None: committed_schema = schema.commit_avro_schema( PROJECT_ID, AVRO_SCHEMA_ID, AVSC_REVISION_FILE ) @@ -270,7 +392,9 @@ def test_get_schema_revsion(avro_schema: str, capsys: CaptureFixture[str]) -> No assert f"{avro_schema}" in out -def test_rollback_schema_revsion(avro_schema: str, capsys: CaptureFixture[str]) -> None: +def test_rollback_schema_revision( + avro_schema: str, capsys: CaptureFixture[str] +) -> None: committed_schema = schema.commit_avro_schema( PROJECT_ID, AVRO_SCHEMA_ID, AVSC_REVISION_FILE ) @@ -283,7 +407,7 @@ def test_rollback_schema_revsion(avro_schema: str, capsys: CaptureFixture[str]) # assert f"{avro_schema}" in out -def test_delete_schema_revsion(avro_schema: str, capsys: CaptureFixture[str]) -> None: +def test_delete_schema_revision(avro_schema: str, capsys: CaptureFixture[str]) -> None: committed_schema = schema.commit_avro_schema( PROJECT_ID, AVRO_SCHEMA_ID, AVSC_REVISION_FILE ) @@ -309,25 +433,34 @@ def test_list_schema_revisions(capsys: CaptureFixture[str]) -> None: def test_create_topic_with_schema( - avro_schema: str, capsys: CaptureFixture[str] + avro_schema: str, + avro_topic_to_create: str, + publisher_client: pubsub_v1.PublisherClient, + capsys: CaptureFixture[str], ) -> None: - schema.create_topic_with_schema(PROJECT_ID, AVRO_TOPIC_ID, AVRO_SCHEMA_ID, "BINARY") + schema.create_topic_with_schema( + PROJECT_ID, AVRO_TOPIC_ID_TO_CREATE, AVRO_SCHEMA_ID, "BINARY" + ) out, _ = capsys.readouterr() assert "Created a topic" in out - assert f"{AVRO_TOPIC_ID}" in out + assert f"{AVRO_TOPIC_ID_TO_CREATE}" in out assert f"{avro_schema}" in out assert "BINARY" in out or "2" in out def test_create_topic_with_schema_revisions( - proto_schema: str, capsys: CaptureFixture[str] + proto_schema: str, + proto_with_revisions_topic_to_create: str, + publisher_client: pubsub_v1.PublisherClient, + capsys: CaptureFixture[str], ) -> None: committed_schema = schema.commit_proto_schema( PROJECT_ID, PROTO_SCHEMA_ID, PROTO_REVISION_FILE ) + schema.create_topic_with_schema_revisions( PROJECT_ID, - PROTO_WITH_REVISIONS_TOPIC_ID, + PROTO_WITH_REVISIONS_TOPIC_ID_TO_CREATE, PROTO_SCHEMA_ID, committed_schema.revision_id, committed_schema.revision_id, @@ -335,17 +468,18 @@ def test_create_topic_with_schema_revisions( ) out, _ = capsys.readouterr() assert "Created a topic" in out - assert f"{PROTO_WITH_REVISIONS_TOPIC_ID}" in out + assert f"{PROTO_WITH_REVISIONS_TOPIC_ID_TO_CREATE}" in out assert f"{proto_schema}" in out assert "BINARY" in out or "2" in out def test_update_topic_schema( - proto_schema: str, proto_topic: str, capsys: CaptureFixture[str] + proto_schema: str, proto_with_revisions_topic: str, capsys: CaptureFixture[str] ) -> None: committed_schema = schema.commit_proto_schema( PROJECT_ID, PROTO_SCHEMA_ID, PROTO_REVISION_FILE ) + schema.update_topic_schema( PROJECT_ID, PROTO_WITH_REVISIONS_TOPIC_ID, @@ -420,8 +554,10 @@ def test_subscribe_with_proto_schema( @typed_flaky -def test_delete_schema(proto_schema: str, capsys: CaptureFixture[str]) -> None: - schema.delete_schema(PROJECT_ID, PROTO_SCHEMA_ID) +def test_delete_schema( + proto_schema_to_delete: str, capsys: CaptureFixture[str] +) -> None: + schema.delete_schema(PROJECT_ID, PROTO_SCHEMA_ID_TO_DELETE) out, _ = capsys.readouterr() assert "Deleted a schema" in out - assert f"{proto_schema}" in out + assert f"{proto_schema_to_delete}" in out diff --git a/samples/snippets/subscriber_test.py b/samples/snippets/subscriber_test.py index 395c50f56..435724782 100644 --- a/samples/snippets/subscriber_test.py +++ b/samples/snippets/subscriber_test.py @@ -86,6 +86,8 @@ def subscription_admin( yield subscription.name + subscriber_client.delete_subscription(request={"subscription": subscription_path}) + @pytest.fixture(scope="module") def topic(publisher_client: pubsub_v1.PublisherClient) -> Generator[str, None, None]: From 9ff7937a7bdf6187bd1a1492e4906e3085b50bb3 Mon Sep 17 00:00:00 2001 From: meredithslota Date: Sat, 1 Apr 2023 15:39:00 +0000 Subject: [PATCH 2/7] ci: do not run 3.6 tests by default (#880) Copied from https://github.com/GoogleCloudPlatform/python-docs-samples/pull/7409 Co-authored-by: Anna Cocuzzo <63511057+acocuzzo@users.noreply.github.com> --- samples/snippets/noxfile_config.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/samples/snippets/noxfile_config.py b/samples/snippets/noxfile_config.py index 32f8b4351..545546d21 100644 --- a/samples/snippets/noxfile_config.py +++ b/samples/snippets/noxfile_config.py @@ -1,4 +1,4 @@ -# Copyright 2020 Google LLC +# Copyright 2021 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,15 +14,15 @@ # Default TEST_CONFIG_OVERRIDE for python repos. -# You can copy this file into your directory, then it will be inported from +# You can copy this file into your directory, then it will be imported from # the noxfile.py. # The source of truth: -# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/noxfile_config.py +# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py TEST_CONFIG_OVERRIDE = { # You can opt out from the test for specific Python versions. - "ignored_versions": ["2.7"], + "ignored_versions": ["2.7", "3.6"], # Old samples are opted out of enforcing Python type hints # All new samples should feature them "enforce_type_hints": True, @@ -32,6 +32,10 @@ # to use your own Cloud project. "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', + # If you need to use a specific version of pip, + # change pip_version_override to the string representation + # of the version number, for example, "20.2.4" + "pip_version_override": None, # A dictionary you want to inject into your test. Don't put any # secrets here. These values will override predefined values. "envs": {}, From 8c590cf8e6c496115a6f46c8099a465649d8f497 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 3 Apr 2023 14:41:07 +0100 Subject: [PATCH 3/7] chore(deps): update all dependencies (#886) Co-authored-by: Anna Cocuzzo <63511057+acocuzzo@users.noreply.github.com> --- samples/snippets/requirements-test.txt | 2 +- samples/snippets/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/snippets/requirements-test.txt b/samples/snippets/requirements-test.txt index 9dca1d1d5..c704f8e3d 100644 --- a/samples/snippets/requirements-test.txt +++ b/samples/snippets/requirements-test.txt @@ -2,4 +2,4 @@ backoff==2.2.1 pytest==7.2.2 mock==5.0.1 flaky==3.7.0 -google-cloud-bigquery==3.7.0 +google-cloud-bigquery==3.9.0 diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index a69515e44..f5ab0c162 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,2 +1,2 @@ -google-cloud-pubsub==2.15.0 +google-cloud-pubsub==2.15.2 avro==1.11.1 From ee2ea7341268fd5428d98208b8af2fc96efe8d03 Mon Sep 17 00:00:00 2001 From: Anna Cocuzzo <63511057+acocuzzo@users.noreply.github.com> Date: Tue, 4 Apr 2023 16:45:33 -0400 Subject: [PATCH 4/7] docs: Fix formatting of request arg in docstring (#894) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: Update gapic-generator-python to v1.8.5 PiperOrigin-RevId: 511892190 Source-Link: https://github.com/googleapis/googleapis/commit/a45d9c09c1287ffdf938f4e8083e791046c0b23b Source-Link: https://github.com/googleapis/googleapis-gen/commit/1907294b1d8365ea24f8c5f2e059a64124c4ed3b Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiMTkwNzI5NGIxZDgzNjVlYTI0ZjhjNWYyZTA1OWE2NDEyNGM0ZWQzYiJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * remove rest.py * chore: Update gapic-generator-python to v1.9.0 PiperOrigin-RevId: 517425588 Source-Link: https://github.com/googleapis/googleapis/commit/33c93eb8b4d3aaf88e44a1be197811052be62282 Source-Link: https://github.com/googleapis/googleapis-gen/commit/d5f59789d19fc43270ff2124967d4ec8992b8e8f Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiZDVmNTk3ODlkMTlmYzQzMjcwZmYyMTI0OTY3ZDRlYzg5OTJiOGU4ZiJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * docs: Fix formatting of request arg in docstring chore: Update gapic-generator-python to v1.9.1 PiperOrigin-RevId: 518604533 Source-Link: https://github.com/googleapis/googleapis/commit/8a085aeddfa010af5bcef090827aac5255383d7e Source-Link: https://github.com/googleapis/googleapis-gen/commit/b2ab4b0a0ae2907e812c209198a74e0898afcb04 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiYjJhYjRiMGEwYWUyOTA3ZTgxMmMyMDkxOThhNzRlMDg5OGFmY2IwNCJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- .../services/publisher/async_client.py | 11 +++---- google/pubsub_v1/services/publisher/client.py | 11 +++---- .../services/schema_service/async_client.py | 21 ++++-------- .../services/schema_service/client.py | 21 ++++-------- .../services/subscriber/async_client.py | 33 ++++++++----------- .../pubsub_v1/services/subscriber/client.py | 33 ++++++++----------- google/pubsub_v1/types/pubsub.py | 2 ++ google/pubsub_v1/types/schema.py | 2 ++ .../snippet_metadata_google.pubsub.v1.json | 2 +- 9 files changed, 55 insertions(+), 81 deletions(-) diff --git a/google/pubsub_v1/services/publisher/async_client.py b/google/pubsub_v1/services/publisher/async_client.py index e749892f5..92a10c38e 100644 --- a/google/pubsub_v1/services/publisher/async_client.py +++ b/google/pubsub_v1/services/publisher/async_client.py @@ -740,7 +740,7 @@ async def sample_list_topics(): Args: request (Optional[Union[google.pubsub_v1.types.ListTopicsRequest, dict]]): - The request object. Request for the `ListTopics` method. + The request object. Request for the ``ListTopics`` method. project (:class:`str`): Required. The name of the project in which to list topics. Format is ``projects/{project-id}``. @@ -866,8 +866,7 @@ async def sample_list_topic_subscriptions(): Args: request (Optional[Union[google.pubsub_v1.types.ListTopicSubscriptionsRequest, dict]]): - The request object. Request for the - `ListTopicSubscriptions` method. + The request object. Request for the ``ListTopicSubscriptions`` method. topic (:class:`str`): Required. The name of the topic that subscriptions are attached to. Format is @@ -998,8 +997,7 @@ async def sample_list_topic_snapshots(): Args: request (Optional[Union[google.pubsub_v1.types.ListTopicSnapshotsRequest, dict]]): - The request object. Request for the `ListTopicSnapshots` - method. + The request object. Request for the ``ListTopicSnapshots`` method. topic (:class:`str`): Required. The name of the topic that snapshots are attached to. Format is @@ -1126,8 +1124,7 @@ async def sample_delete_topic(): Args: request (Optional[Union[google.pubsub_v1.types.DeleteTopicRequest, dict]]): - The request object. Request for the `DeleteTopic` - method. + The request object. Request for the ``DeleteTopic`` method. topic (:class:`str`): Required. Name of the topic to delete. Format is ``projects/{project}/topics/{topic}``. diff --git a/google/pubsub_v1/services/publisher/client.py b/google/pubsub_v1/services/publisher/client.py index a9684144f..32549cff7 100644 --- a/google/pubsub_v1/services/publisher/client.py +++ b/google/pubsub_v1/services/publisher/client.py @@ -976,7 +976,7 @@ def sample_list_topics(): Args: request (Union[google.pubsub_v1.types.ListTopicsRequest, dict]): - The request object. Request for the `ListTopics` method. + The request object. Request for the ``ListTopics`` method. project (str): Required. The name of the project in which to list topics. Format is ``projects/{project-id}``. @@ -1091,8 +1091,7 @@ def sample_list_topic_subscriptions(): Args: request (Union[google.pubsub_v1.types.ListTopicSubscriptionsRequest, dict]): - The request object. Request for the - `ListTopicSubscriptions` method. + The request object. Request for the ``ListTopicSubscriptions`` method. topic (str): Required. The name of the topic that subscriptions are attached to. Format is @@ -1212,8 +1211,7 @@ def sample_list_topic_snapshots(): Args: request (Union[google.pubsub_v1.types.ListTopicSnapshotsRequest, dict]): - The request object. Request for the `ListTopicSnapshots` - method. + The request object. Request for the ``ListTopicSnapshots`` method. topic (str): Required. The name of the topic that snapshots are attached to. Format is @@ -1329,8 +1327,7 @@ def sample_delete_topic(): Args: request (Union[google.pubsub_v1.types.DeleteTopicRequest, dict]): - The request object. Request for the `DeleteTopic` - method. + The request object. Request for the ``DeleteTopic`` method. topic (str): Required. Name of the topic to delete. Format is ``projects/{project}/topics/{topic}``. diff --git a/google/pubsub_v1/services/schema_service/async_client.py b/google/pubsub_v1/services/schema_service/async_client.py index 68b896e48..cfb566e84 100644 --- a/google/pubsub_v1/services/schema_service/async_client.py +++ b/google/pubsub_v1/services/schema_service/async_client.py @@ -486,8 +486,7 @@ async def sample_list_schemas(): Args: request (Optional[Union[google.pubsub_v1.types.ListSchemasRequest, dict]]): - The request object. Request for the `ListSchemas` - method. + The request object. Request for the ``ListSchemas`` method. parent (:class:`str`): Required. The name of the project in which to list schemas. Format is ``projects/{project-id}``. @@ -600,8 +599,7 @@ async def sample_list_schema_revisions(): Args: request (Optional[Union[google.pubsub_v1.types.ListSchemaRevisionsRequest, dict]]): - The request object. Request for the - `ListSchemaRevisions` method. + The request object. Request for the ``ListSchemaRevisions`` method. name (:class:`str`): Required. The name of the schema to list revisions for. @@ -829,8 +827,7 @@ async def sample_rollback_schema(): Args: request (Optional[Union[google.pubsub_v1.types.RollbackSchemaRequest, dict]]): - The request object. Request for the `RollbackSchema` - method. + The request object. Request for the ``RollbackSchema`` method. name (:class:`str`): Required. The schema being rolled back with revision id. @@ -941,8 +938,7 @@ async def sample_delete_schema_revision(): Args: request (Optional[Union[google.pubsub_v1.types.DeleteSchemaRevisionRequest, dict]]): - The request object. Request for the - `DeleteSchemaRevision` method. + The request object. Request for the ``DeleteSchemaRevision`` method. name (:class:`str`): Required. The name of the schema revision to be deleted, with a revision ID explicitly included. @@ -1050,8 +1046,7 @@ async def sample_delete_schema(): Args: request (Optional[Union[google.pubsub_v1.types.DeleteSchemaRequest, dict]]): - The request object. Request for the `DeleteSchema` - method. + The request object. Request for the ``DeleteSchema`` method. name (:class:`str`): Required. Name of the schema to delete. Format is ``projects/{project}/schemas/{schema}``. @@ -1148,8 +1143,7 @@ async def sample_validate_schema(): Args: request (Optional[Union[google.pubsub_v1.types.ValidateSchemaRequest, dict]]): - The request object. Request for the `ValidateSchema` - method. + The request object. Request for the ``ValidateSchema`` method. parent (:class:`str`): Required. The name of the project in which to validate schemas. Format is ``projects/{project-id}``. @@ -1259,8 +1253,7 @@ async def sample_validate_message(): Args: request (Optional[Union[google.pubsub_v1.types.ValidateMessageRequest, dict]]): - The request object. Request for the `ValidateMessage` - method. + The request object. Request for the ``ValidateMessage`` method. retry (google.api_core.retry.Retry): Designation of what errors, if any, should be retried. timeout (float): The timeout for this request. diff --git a/google/pubsub_v1/services/schema_service/client.py b/google/pubsub_v1/services/schema_service/client.py index d217c49f0..2944caed9 100644 --- a/google/pubsub_v1/services/schema_service/client.py +++ b/google/pubsub_v1/services/schema_service/client.py @@ -718,8 +718,7 @@ def sample_list_schemas(): Args: request (Union[google.pubsub_v1.types.ListSchemasRequest, dict]): - The request object. Request for the `ListSchemas` - method. + The request object. Request for the ``ListSchemas`` method. parent (str): Required. The name of the project in which to list schemas. Format is ``projects/{project-id}``. @@ -832,8 +831,7 @@ def sample_list_schema_revisions(): Args: request (Union[google.pubsub_v1.types.ListSchemaRevisionsRequest, dict]): - The request object. Request for the - `ListSchemaRevisions` method. + The request object. Request for the ``ListSchemaRevisions`` method. name (str): Required. The name of the schema to list revisions for. @@ -1061,8 +1059,7 @@ def sample_rollback_schema(): Args: request (Union[google.pubsub_v1.types.RollbackSchemaRequest, dict]): - The request object. Request for the `RollbackSchema` - method. + The request object. Request for the ``RollbackSchema`` method. name (str): Required. The schema being rolled back with revision id. @@ -1173,8 +1170,7 @@ def sample_delete_schema_revision(): Args: request (Union[google.pubsub_v1.types.DeleteSchemaRevisionRequest, dict]): - The request object. Request for the - `DeleteSchemaRevision` method. + The request object. Request for the ``DeleteSchemaRevision`` method. name (str): Required. The name of the schema revision to be deleted, with a revision ID explicitly included. @@ -1282,8 +1278,7 @@ def sample_delete_schema(): Args: request (Union[google.pubsub_v1.types.DeleteSchemaRequest, dict]): - The request object. Request for the `DeleteSchema` - method. + The request object. Request for the ``DeleteSchema`` method. name (str): Required. Name of the schema to delete. Format is ``projects/{project}/schemas/{schema}``. @@ -1380,8 +1375,7 @@ def sample_validate_schema(): Args: request (Union[google.pubsub_v1.types.ValidateSchemaRequest, dict]): - The request object. Request for the `ValidateSchema` - method. + The request object. Request for the ``ValidateSchema`` method. parent (str): Required. The name of the project in which to validate schemas. Format is ``projects/{project-id}``. @@ -1491,8 +1485,7 @@ def sample_validate_message(): Args: request (Union[google.pubsub_v1.types.ValidateMessageRequest, dict]): - The request object. Request for the `ValidateMessage` - method. + The request object. Request for the ``ValidateMessage`` method. retry (google.api_core.retry.Retry): Designation of what errors, if any, should be retried. timeout (float): The timeout for this request. diff --git a/google/pubsub_v1/services/subscriber/async_client.py b/google/pubsub_v1/services/subscriber/async_client.py index dbe4fd0e7..694b166d9 100644 --- a/google/pubsub_v1/services/subscriber/async_client.py +++ b/google/pubsub_v1/services/subscriber/async_client.py @@ -279,10 +279,10 @@ async def sample_create_subscription(): Args: request (Optional[Union[google.pubsub_v1.types.Subscription, dict]]): - The request object. A subscription resource. If none of - `push_config` or `bigquery_config` is set, then the - subscriber will pull and ack messages using API methods. - At most one of these fields may be set. + The request object. A subscription resource. If none of ``push_config`` or + ``bigquery_config`` is set, then the subscriber will + pull and ack messages using API methods. At most one of + these fields may be set. name (:class:`str`): Required. The name of the subscription. It must have the format @@ -706,8 +706,7 @@ async def sample_list_subscriptions(): Args: request (Optional[Union[google.pubsub_v1.types.ListSubscriptionsRequest, dict]]): - The request object. Request for the `ListSubscriptions` - method. + The request object. Request for the ``ListSubscriptions`` method. project (:class:`str`): Required. The name of the project in which to list subscriptions. Format is ``projects/{project-id}``. @@ -1194,7 +1193,7 @@ async def sample_pull(): Args: request (Optional[Union[google.pubsub_v1.types.PullRequest, dict]]): - The request object. Request for the `Pull` method. + The request object. Request for the ``Pull`` method. subscription (:class:`str`): Required. The subscription from which messages should be pulled. Format is @@ -1360,11 +1359,10 @@ def request_generator(): Args: requests (AsyncIterator[`google.pubsub_v1.types.StreamingPullRequest`]): - The request object AsyncIterator. Request for the `StreamingPull` - streaming RPC method. This request is used to establish - the initial stream as well as to stream acknowledgements - and ack deadline modifications from the client to the - server. + The request object AsyncIterator. Request for the ``StreamingPull`` streaming RPC method. + This request is used to establish the initial stream as + well as to stream acknowledgements and ack deadline + modifications from the client to the server. retry (google.api_core.retry.Retry): Designation of what errors, if any, should be retried. timeout (float): The timeout for this request. @@ -1697,8 +1695,7 @@ async def sample_list_snapshots(): Args: request (Optional[Union[google.pubsub_v1.types.ListSnapshotsRequest, dict]]): - The request object. Request for the `ListSnapshots` - method. + The request object. Request for the ``ListSnapshots`` method. project (:class:`str`): Required. The name of the project in which to list snapshots. Format is ``projects/{project-id}``. @@ -1841,8 +1838,7 @@ async def sample_create_snapshot(): Args: request (Optional[Union[google.pubsub_v1.types.CreateSnapshotRequest, dict]]): - The request object. Request for the `CreateSnapshot` - method. + The request object. Request for the ``CreateSnapshot`` method. name (:class:`str`): Required. User-provided name for this snapshot. If the name is not provided in the request, the server will @@ -2116,8 +2112,7 @@ async def sample_delete_snapshot(): Args: request (Optional[Union[google.pubsub_v1.types.DeleteSnapshotRequest, dict]]): - The request object. Request for the `DeleteSnapshot` - method. + The request object. Request for the ``DeleteSnapshot`` method. snapshot (:class:`str`): Required. The name of the snapshot to delete. Format is ``projects/{project}/snapshots/{snap}``. @@ -2225,7 +2220,7 @@ async def sample_seek(): Args: request (Optional[Union[google.pubsub_v1.types.SeekRequest, dict]]): - The request object. Request for the `Seek` method. + The request object. Request for the ``Seek`` method. retry (google.api_core.retry.Retry): Designation of what errors, if any, should be retried. timeout (float): The timeout for this request. diff --git a/google/pubsub_v1/services/subscriber/client.py b/google/pubsub_v1/services/subscriber/client.py index 816275ef7..a3237d122 100644 --- a/google/pubsub_v1/services/subscriber/client.py +++ b/google/pubsub_v1/services/subscriber/client.py @@ -558,10 +558,10 @@ def sample_create_subscription(): Args: request (Union[google.pubsub_v1.types.Subscription, dict]): - The request object. A subscription resource. If none of - `push_config` or `bigquery_config` is set, then the - subscriber will pull and ack messages using API methods. - At most one of these fields may be set. + The request object. A subscription resource. If none of ``push_config`` or + ``bigquery_config`` is set, then the subscriber will + pull and ack messages using API methods. At most one of + these fields may be set. name (str): Required. The name of the subscription. It must have the format @@ -954,8 +954,7 @@ def sample_list_subscriptions(): Args: request (Union[google.pubsub_v1.types.ListSubscriptionsRequest, dict]): - The request object. Request for the `ListSubscriptions` - method. + The request object. Request for the ``ListSubscriptions`` method. project (str): Required. The name of the project in which to list subscriptions. Format is ``projects/{project-id}``. @@ -1404,7 +1403,7 @@ def sample_pull(): Args: request (Union[google.pubsub_v1.types.PullRequest, dict]): - The request object. Request for the `Pull` method. + The request object. Request for the ``Pull`` method. subscription (str): Required. The subscription from which messages should be pulled. Format is @@ -1558,11 +1557,10 @@ def request_generator(): Args: requests (Iterator[google.pubsub_v1.types.StreamingPullRequest]): - The request object iterator. Request for the `StreamingPull` - streaming RPC method. This request is used to establish - the initial stream as well as to stream acknowledgements - and ack deadline modifications from the client to the - server. + The request object iterator. Request for the ``StreamingPull`` streaming RPC method. + This request is used to establish the initial stream as + well as to stream acknowledgements and ack deadline + modifications from the client to the server. retry (google.api_core.retry.Retry): Designation of what errors, if any, should be retried. timeout (float): The timeout for this request. @@ -1863,8 +1861,7 @@ def sample_list_snapshots(): Args: request (Union[google.pubsub_v1.types.ListSnapshotsRequest, dict]): - The request object. Request for the `ListSnapshots` - method. + The request object. Request for the ``ListSnapshots`` method. project (str): Required. The name of the project in which to list snapshots. Format is ``projects/{project-id}``. @@ -1996,8 +1993,7 @@ def sample_create_snapshot(): Args: request (Union[google.pubsub_v1.types.CreateSnapshotRequest, dict]): - The request object. Request for the `CreateSnapshot` - method. + The request object. Request for the ``CreateSnapshot`` method. name (str): Required. User-provided name for this snapshot. If the name is not provided in the request, the server will @@ -2253,8 +2249,7 @@ def sample_delete_snapshot(): Args: request (Union[google.pubsub_v1.types.DeleteSnapshotRequest, dict]): - The request object. Request for the `DeleteSnapshot` - method. + The request object. Request for the ``DeleteSnapshot`` method. snapshot (str): Required. The name of the snapshot to delete. Format is ``projects/{project}/snapshots/{snap}``. @@ -2353,7 +2348,7 @@ def sample_seek(): Args: request (Union[google.pubsub_v1.types.SeekRequest, dict]): - The request object. Request for the `Seek` method. + The request object. Request for the ``Seek`` method. retry (google.api_core.retry.Retry): Designation of what errors, if any, should be retried. timeout (float): The timeout for this request. diff --git a/google/pubsub_v1/types/pubsub.py b/google/pubsub_v1/types/pubsub.py index 3e2f225ad..cee8cee4c 100644 --- a/google/pubsub_v1/types/pubsub.py +++ b/google/pubsub_v1/types/pubsub.py @@ -13,6 +13,8 @@ # 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 diff --git a/google/pubsub_v1/types/schema.py b/google/pubsub_v1/types/schema.py index 27a6efbbd..7b432906b 100644 --- a/google/pubsub_v1/types/schema.py +++ b/google/pubsub_v1/types/schema.py @@ -13,6 +13,8 @@ # 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 diff --git a/samples/generated_samples/snippet_metadata_google.pubsub.v1.json b/samples/generated_samples/snippet_metadata_google.pubsub.v1.json index 256ef8a94..d66015ac4 100644 --- a/samples/generated_samples/snippet_metadata_google.pubsub.v1.json +++ b/samples/generated_samples/snippet_metadata_google.pubsub.v1.json @@ -8,7 +8,7 @@ ], "language": "PYTHON", "name": "google-cloud-pubsub", - "version": "2.15.2" + "version": "0.1.0" }, "snippets": [ { From 0b51b1b21a024f8d8e6ba9cbb71d44b4f7e14195 Mon Sep 17 00:00:00 2001 From: Anna Cocuzzo <63511057+acocuzzo@users.noreply.github.com> Date: Wed, 5 Apr 2023 09:44:12 -0400 Subject: [PATCH 5/7] CI: Add typed_flaky to streaming_pull system tests (#895) --- noxfile.py | 1 + owlbot.py | 2 +- tests/system.py | 119 ++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 98 insertions(+), 24 deletions(-) diff --git a/noxfile.py b/noxfile.py index 574fbd644..35e5916a9 100644 --- a/noxfile.py +++ b/noxfile.py @@ -55,6 +55,7 @@ ] SYSTEM_TEST_EXTERNAL_DEPENDENCIES = [ "psutil", + "flaky", ] SYSTEM_TEST_LOCAL_DEPENDENCIES = [] SYSTEM_TEST_DEPENDENCIES = [] diff --git a/owlbot.py b/owlbot.py index 8787d90c6..7539adfdb 100644 --- a/owlbot.py +++ b/owlbot.py @@ -338,7 +338,7 @@ versions=gcp.common.detect_versions(path="./google", default_first=True), unit_test_python_versions=["3.7", "3.8", "3.9", "3.10", "3.11"], system_test_python_versions=["3.10"], - system_test_external_dependencies=["psutil"], + system_test_external_dependencies=["psutil","flaky"], ) s.move(templated_files, excludes=[".coveragerc", ".github/release-please.yml", "README.rst", "docs/index.rst"]) diff --git a/tests/system.py b/tests/system.py index d7f7c5bea..bb1265453 100644 --- a/tests/system.py +++ b/tests/system.py @@ -23,6 +23,7 @@ import sys import threading import time +from typing import Any, Callable, cast, TypeVar # special case python < 3.8 if sys.version_info.major == 3 and sys.version_info.minor < 8: @@ -30,6 +31,7 @@ else: from unittest import mock +from flaky import flaky import pytest import google.auth @@ -43,6 +45,9 @@ from test_utils.system import unique_resource_id +C = TypeVar("C", bound=Callable[..., Any]) +typed_flaky = cast(Callable[[C], C], flaky(max_runs=3, min_passes=1)) + @pytest.fixture(scope="module") def project(): @@ -61,13 +66,13 @@ def subscriber(request): @pytest.fixture -def topic_path(project, publisher): +def topic_path_base(project, publisher): topic_name = "t" + unique_resource_id("-") yield publisher.topic_path(project, topic_name) @pytest.fixture -def subscription_path(project, subscriber): +def subscription_path_base(project, subscriber): sub_name = "s" + unique_resource_id("-") yield subscriber.subscription_path(project, sub_name) @@ -82,7 +87,9 @@ def cleanup(): to_call(*args, **kwargs) -def test_publish_messages(publisher, topic_path, cleanup): +def test_publish_messages(publisher, topic_path_base, cleanup): + # Customize topic path to test. + topic_path = topic_path_base + "-publish-messages" # Make sure the topic gets deleted. cleanup.append((publisher.delete_topic, (), {"topic": topic_path})) @@ -100,7 +107,9 @@ def test_publish_messages(publisher, topic_path, cleanup): assert isinstance(result, str) -def test_publish_large_messages(publisher, topic_path, cleanup): +def test_publish_large_messages(publisher, topic_path_base, cleanup): + # Customize topic path to test. + topic_path = topic_path_base + "-publish-large-messages" # Make sure the topic gets deleted. cleanup.append((publisher.delete_topic, (), {"topic": topic_path})) @@ -130,8 +139,11 @@ def test_publish_large_messages(publisher, topic_path, cleanup): def test_subscribe_to_messages( - publisher, topic_path, subscriber, subscription_path, cleanup + publisher, topic_path_base, subscriber, subscription_path_base, cleanup ): + # Customize topic path to test. + topic_path = topic_path_base + "-subscribe-to-messages" + subscription_path = subscription_path_base + "-subscribe-to-messages" # Make sure the topic and subscription get deleted. cleanup.append((publisher.delete_topic, (), {"topic": topic_path})) cleanup.append( @@ -175,8 +187,12 @@ def test_subscribe_to_messages( def test_subscribe_to_messages_async_callbacks( - publisher, topic_path, subscriber, subscription_path, cleanup + publisher, topic_path_base, subscriber, subscription_path_base, cleanup ): + # Customize topic path to test. + custom_str = "-subscribe-to-messages-async-callback" + topic_path = topic_path_base + custom_str + subscription_path = subscription_path_base + custom_str # Make sure the topic and subscription get deleted. cleanup.append((publisher.delete_topic, (), {"topic": topic_path})) cleanup.append( @@ -227,8 +243,12 @@ def test_subscribe_to_messages_async_callbacks( def test_creating_subscriptions_with_non_default_settings( - publisher, subscriber, project, topic_path, subscription_path, cleanup + publisher, subscriber, project, topic_path_base, subscription_path_base, cleanup ): + # Customize topic path to test. + custom_str = "-creating-subscriptions-with-non-default-settings" + topic_path = topic_path_base + custom_str + subscription_path = subscription_path_base + custom_str # Make sure the topic and subscription get deleted. cleanup.append((publisher.delete_topic, (), {"topic": topic_path})) cleanup.append( @@ -346,7 +366,8 @@ def test_listing_topic_subscriptions(publisher, subscriber, project, cleanup): assert subscriptions == {subscription_paths[0], subscription_paths[2]} -def test_managing_topic_iam_policy(publisher, topic_path, cleanup): +def test_managing_topic_iam_policy(publisher, topic_path_base, cleanup): + topic_path = topic_path_base + "-managing-topic-iam-policy" cleanup.append((publisher.delete_topic, (), {"topic": topic_path})) # create a topic and customize its policy @@ -375,8 +396,11 @@ def test_managing_topic_iam_policy(publisher, topic_path, cleanup): def test_managing_subscription_iam_policy( - publisher, subscriber, topic_path, subscription_path, cleanup + publisher, subscriber, topic_path_base, subscription_path_base, cleanup ): + custom_str = "-managing-subscription-iam-policy" + topic_path = topic_path_base + custom_str + subscription_path = subscription_path_base + custom_str # Make sure the topic and subscription get deleted. cleanup.append((publisher.delete_topic, (), {"topic": topic_path})) cleanup.append( @@ -410,7 +434,7 @@ def test_managing_subscription_iam_policy( def test_subscriber_not_leaking_open_sockets( - publisher, topic_path, subscription_path, cleanup + publisher, topic_path_base, subscription_path_base, cleanup ): # Make sure the topic and the supscription get deleted. # NOTE: Since subscriber client will be closed in the test, we should not @@ -419,8 +443,12 @@ def test_subscriber_not_leaking_open_sockets( # Also, since the client will get closed, we need another subscriber client # to clean up the subscription. We also need to make sure that auxiliary # subscriber releases the sockets, too. + custom_str = "-not-leaking-open-sockets" + subscription_path = subscription_path_base + custom_str + topic_path = topic_path_base + custom_str subscriber = pubsub_v1.SubscriberClient(transport="grpc") subscriber_2 = pubsub_v1.SubscriberClient(transport="grpc") + cleanup.append( (subscriber_2.delete_subscription, (), {"subscription": subscription_path}) ) @@ -460,8 +488,11 @@ def test_subscriber_not_leaking_open_sockets( def test_synchronous_pull_no_deadline_error_if_no_messages( - publisher, topic_path, subscriber, subscription_path, cleanup + publisher, topic_path_base, subscriber, subscription_path_base, cleanup ): + custom_str = "-synchronous-pull-deadline-error-if-no-messages" + topic_path = topic_path_base + custom_str + subscription_path = subscription_path_base + custom_str # Make sure the topic and subscription get deleted. cleanup.append((publisher.delete_topic, (), {"topic": topic_path})) cleanup.append( @@ -485,8 +516,11 @@ def test_synchronous_pull_no_deadline_error_if_no_messages( class TestStreamingPull(object): def test_streaming_pull_callback_error_propagation( - self, publisher, topic_path, subscriber, subscription_path, cleanup + self, publisher, topic_path_base, subscriber, subscription_path_base, cleanup ): + custom_str = "-streaming-pull-callback-error-propagation" + topic_path = topic_path_base + custom_str + subscription_path = subscription_path_base + custom_str # Make sure the topic and subscription get deleted. cleanup.append((publisher.delete_topic, (), {"topic": topic_path})) cleanup.append( @@ -512,9 +546,19 @@ class CallbackError(Exception): with pytest.raises(CallbackError): future.result(timeout=30) + @typed_flaky def test_streaming_pull_ack_deadline( - self, publisher, subscriber, project, topic_path, subscription_path, cleanup + self, + publisher, + subscriber, + project, + topic_path_base, + subscription_path_base, + cleanup, ): + custom_str = "-streaming-pull-ack-deadline" + topic_path = topic_path_base + custom_str + subscription_path = subscription_path_base + custom_str # Make sure the topic and subscription get deleted. cleanup.append((publisher.delete_topic, (), {"topic": topic_path})) cleanup.append( @@ -564,8 +608,11 @@ def test_streaming_pull_ack_deadline( subscription_future.cancel() def test_streaming_pull_max_messages( - self, publisher, topic_path, subscriber, subscription_path, cleanup + self, publisher, topic_path_base, subscriber, subscription_path_base, cleanup ): + custom_str = "-streaming-pull-max-messages" + topic_path = topic_path_base + custom_str + subscription_path = subscription_path_base + custom_str # Make sure the topic and subscription get deleted. cleanup.append((publisher.delete_topic, (), {"topic": topic_path})) cleanup.append( @@ -619,9 +666,13 @@ def test_streaming_pull_max_messages( finally: subscription_future.cancel() # trigger clean shutdown + @typed_flaky def test_streaming_pull_blocking_shutdown( - self, publisher, topic_path, subscriber, subscription_path, cleanup + self, publisher, topic_path_base, subscriber, subscription_path_base, cleanup ): + custom_str = "-streaming-pull-blocking-shutdown" + topic_path = topic_path_base + custom_str + subscription_path = subscription_path_base + custom_str # Make sure the topic and subscription get deleted. cleanup.append((publisher.delete_topic, (), {"topic": topic_path})) cleanup.append( @@ -702,9 +753,11 @@ def callback2(message): ) class TestBasicRBAC(object): def test_streaming_pull_subscriber_permissions_sufficient( - self, publisher, topic_path, subscriber, subscription_path, cleanup + self, publisher, topic_path_base, subscriber, subscription_path_base, cleanup ): - + custom_str = "-streaming-pull-subscriber-permissions-sufficient" + topic_path = topic_path_base + custom_str + subscription_path = subscription_path_base + custom_str # Make sure the topic and subscription get deleted. cleanup.append((publisher.delete_topic, (), {"topic": topic_path})) cleanup.append( @@ -739,9 +792,11 @@ def test_streaming_pull_subscriber_permissions_sufficient( future.cancel() def test_publisher_role_can_publish_messages( - self, publisher, topic_path, subscriber, subscription_path, cleanup + self, publisher, topic_path_base, subscriber, subscription_path_base, cleanup ): - + custom_str = "-publisher-role-can-publish-messages" + topic_path = topic_path_base + custom_str + subscription_path = subscription_path_base + custom_str # Make sure the topic and subscription get deleted. cleanup.append((publisher.delete_topic, (), {"topic": topic_path})) cleanup.append( @@ -767,8 +822,17 @@ def test_publisher_role_can_publish_messages( "Snapshot creation is not instant on the backend, causing test falkiness." ) def test_snapshot_seek_subscriber_permissions_sufficient( - self, project, publisher, topic_path, subscriber, subscription_path, cleanup + self, + project, + publisher, + topic_path_base, + subscriber, + subscription_path_base, + cleanup, ): + custom_str = "-snapshot-seek-subscriber-permissions-sufficient" + topic_path = topic_path_base + custom_str + subscription_path = subscription_path_base + custom_str snapshot_name = "snap" + unique_resource_id("-") snapshot_path = "projects/{}/snapshots/{}".format(project, snapshot_name) @@ -813,10 +877,10 @@ def test_snapshot_seek_subscriber_permissions_sufficient( assert len(response.received_messages) == 1 def test_viewer_role_can_list_resources( - self, project, publisher, topic_path, subscriber, cleanup + self, project, publisher, topic_path_base, subscriber, cleanup ): project_path = "projects/" + project - + topic_path = topic_path_base + "-viewer-role-can-list-resources" # Make sure the created topic gets deleted. cleanup.append((publisher.delete_topic, (), {"topic": topic_path})) @@ -844,8 +908,17 @@ def test_viewer_role_can_list_resources( next(iter(viewer_only_subscriber.list_snapshots(project=project_path)), None) def test_editor_role_can_create_resources( - self, project, publisher, topic_path, subscriber, subscription_path, cleanup + self, + project, + publisher, + topic_path_base, + subscriber, + subscription_path_base, + cleanup, ): + custom_str = "-editor-role-can-create-resources" + topic_path = topic_path_base + custom_str + subscription_path = subscription_path_base + custom_str snapshot_name = "snap" + unique_resource_id("-") snapshot_path = "projects/{}/snapshots/{}".format(project, snapshot_name) From a80c1d1f6f880cd13c247231bdc86c824edab8cb Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Thu, 6 Apr 2023 10:04:29 -0400 Subject: [PATCH 6/7] feat: enable "rest" transport in Python for services supporting numeric enums (#863) --- google/pubsub_v1/gapic_metadata.json | 190 + google/pubsub_v1/services/publisher/client.py | 2 + .../services/publisher/transports/__init__.py | 5 + .../services/publisher/transports/rest.py | 1612 ++++++ .../services/schema_service/client.py | 2 + .../schema_service/transports/__init__.py | 5 + .../schema_service/transports/rest.py | 1763 +++++++ .../pubsub_v1/services/subscriber/client.py | 2 + .../subscriber/transports/__init__.py | 5 + .../services/subscriber/transports/rest.py | 2368 +++++++++ google/pubsub_v1/types/pubsub.py | 8 +- tests/system.py | 37 +- tests/unit/gapic/pubsub_v1/test_publisher.py | 3040 ++++++++++- .../gapic/pubsub_v1/test_schema_service.py | 3372 +++++++++++- tests/unit/gapic/pubsub_v1/test_subscriber.py | 4694 ++++++++++++++++- 15 files changed, 16681 insertions(+), 424 deletions(-) create mode 100644 google/pubsub_v1/services/publisher/transports/rest.py create mode 100644 google/pubsub_v1/services/schema_service/transports/rest.py create mode 100644 google/pubsub_v1/services/subscriber/transports/rest.py diff --git a/google/pubsub_v1/gapic_metadata.json b/google/pubsub_v1/gapic_metadata.json index ac814d065..4a8f51a51 100644 --- a/google/pubsub_v1/gapic_metadata.json +++ b/google/pubsub_v1/gapic_metadata.json @@ -106,6 +106,56 @@ ] } } + }, + "rest": { + "libraryClient": "PublisherClient", + "rpcs": { + "CreateTopic": { + "methods": [ + "create_topic" + ] + }, + "DeleteTopic": { + "methods": [ + "delete_topic" + ] + }, + "DetachSubscription": { + "methods": [ + "detach_subscription" + ] + }, + "GetTopic": { + "methods": [ + "get_topic" + ] + }, + "ListTopicSnapshots": { + "methods": [ + "list_topic_snapshots" + ] + }, + "ListTopicSubscriptions": { + "methods": [ + "list_topic_subscriptions" + ] + }, + "ListTopics": { + "methods": [ + "list_topics" + ] + }, + "Publish": { + "methods": [ + "publish" + ] + }, + "UpdateTopic": { + "methods": [ + "update_topic" + ] + } + } } } }, @@ -220,6 +270,61 @@ ] } } + }, + "rest": { + "libraryClient": "SchemaServiceClient", + "rpcs": { + "CommitSchema": { + "methods": [ + "commit_schema" + ] + }, + "CreateSchema": { + "methods": [ + "create_schema" + ] + }, + "DeleteSchema": { + "methods": [ + "delete_schema" + ] + }, + "DeleteSchemaRevision": { + "methods": [ + "delete_schema_revision" + ] + }, + "GetSchema": { + "methods": [ + "get_schema" + ] + }, + "ListSchemaRevisions": { + "methods": [ + "list_schema_revisions" + ] + }, + "ListSchemas": { + "methods": [ + "list_schemas" + ] + }, + "RollbackSchema": { + "methods": [ + "rollback_schema" + ] + }, + "ValidateMessage": { + "methods": [ + "validate_message" + ] + }, + "ValidateSchema": { + "methods": [ + "validate_schema" + ] + } + } } } }, @@ -394,6 +499,91 @@ ] } } + }, + "rest": { + "libraryClient": "SubscriberClient", + "rpcs": { + "Acknowledge": { + "methods": [ + "acknowledge" + ] + }, + "CreateSnapshot": { + "methods": [ + "create_snapshot" + ] + }, + "CreateSubscription": { + "methods": [ + "create_subscription" + ] + }, + "DeleteSnapshot": { + "methods": [ + "delete_snapshot" + ] + }, + "DeleteSubscription": { + "methods": [ + "delete_subscription" + ] + }, + "GetSnapshot": { + "methods": [ + "get_snapshot" + ] + }, + "GetSubscription": { + "methods": [ + "get_subscription" + ] + }, + "ListSnapshots": { + "methods": [ + "list_snapshots" + ] + }, + "ListSubscriptions": { + "methods": [ + "list_subscriptions" + ] + }, + "ModifyAckDeadline": { + "methods": [ + "modify_ack_deadline" + ] + }, + "ModifyPushConfig": { + "methods": [ + "modify_push_config" + ] + }, + "Pull": { + "methods": [ + "pull" + ] + }, + "Seek": { + "methods": [ + "seek" + ] + }, + "StreamingPull": { + "methods": [ + "streaming_pull" + ] + }, + "UpdateSnapshot": { + "methods": [ + "update_snapshot" + ] + }, + "UpdateSubscription": { + "methods": [ + "update_subscription" + ] + } + } } } } diff --git a/google/pubsub_v1/services/publisher/client.py b/google/pubsub_v1/services/publisher/client.py index 32549cff7..98caee051 100644 --- a/google/pubsub_v1/services/publisher/client.py +++ b/google/pubsub_v1/services/publisher/client.py @@ -60,6 +60,7 @@ from .transports.base import PublisherTransport, DEFAULT_CLIENT_INFO from .transports.grpc import PublisherGrpcTransport from .transports.grpc_asyncio import PublisherGrpcAsyncIOTransport +from .transports.rest import PublisherRestTransport class PublisherClientMeta(type): @@ -73,6 +74,7 @@ class PublisherClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[PublisherTransport]] _transport_registry["grpc"] = PublisherGrpcTransport _transport_registry["grpc_asyncio"] = PublisherGrpcAsyncIOTransport + _transport_registry["rest"] = PublisherRestTransport def get_transport_class( cls, diff --git a/google/pubsub_v1/services/publisher/transports/__init__.py b/google/pubsub_v1/services/publisher/transports/__init__.py index e73fe8901..8a2b06839 100644 --- a/google/pubsub_v1/services/publisher/transports/__init__.py +++ b/google/pubsub_v1/services/publisher/transports/__init__.py @@ -19,15 +19,20 @@ from .base import PublisherTransport from .grpc import PublisherGrpcTransport from .grpc_asyncio import PublisherGrpcAsyncIOTransport +from .rest import PublisherRestTransport +from .rest import PublisherRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[PublisherTransport]] _transport_registry["grpc"] = PublisherGrpcTransport _transport_registry["grpc_asyncio"] = PublisherGrpcAsyncIOTransport +_transport_registry["rest"] = PublisherRestTransport __all__ = ( "PublisherTransport", "PublisherGrpcTransport", "PublisherGrpcAsyncIOTransport", + "PublisherRestTransport", + "PublisherRestInterceptor", ) diff --git a/google/pubsub_v1/services/publisher/transports/rest.py b/google/pubsub_v1/services/publisher/transports/rest.py new file mode 100644 index 000000000..fc31ce681 --- /dev/null +++ b/google/pubsub_v1/services/publisher/transports/rest.py @@ -0,0 +1,1612 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # 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 +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.iam.v1 import iam_policy_pb2 # type: ignore +from google.iam.v1 import policy_pb2 # type: ignore +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.iam.v1 import iam_policy_pb2 # type: ignore +from google.iam.v1 import policy_pb2 # type: ignore +from google.protobuf import empty_pb2 # type: ignore +from google.pubsub_v1.types import pubsub + +from .base import PublisherTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class PublisherRestInterceptor: + """Interceptor for Publisher. + + 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 PublisherRestTransport. + + .. code-block:: python + class MyCustomPublisherInterceptor(PublisherRestInterceptor): + def pre_create_topic(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_topic(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_topic(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_detach_subscription(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_detach_subscription(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_get_topic(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_topic(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_topics(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_topics(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_topic_snapshots(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_topic_snapshots(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_topic_subscriptions(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_topic_subscriptions(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_publish(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_publish(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_topic(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_topic(self, response): + logging.log(f"Received response: {response}") + return response + + transport = PublisherRestTransport(interceptor=MyCustomPublisherInterceptor()) + client = PublisherClient(transport=transport) + + + """ + + def pre_create_topic( + self, request: pubsub.Topic, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[pubsub.Topic, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_topic + + Override in a subclass to manipulate the request or metadata + before they are sent to the Publisher server. + """ + return request, metadata + + def post_create_topic(self, response: pubsub.Topic) -> pubsub.Topic: + """Post-rpc interceptor for create_topic + + Override in a subclass to manipulate the response + after it is returned by the Publisher server but before + it is returned to user code. + """ + return response + + def pre_delete_topic( + self, request: pubsub.DeleteTopicRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[pubsub.DeleteTopicRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_topic + + Override in a subclass to manipulate the request or metadata + before they are sent to the Publisher server. + """ + return request, metadata + + def pre_detach_subscription( + self, + request: pubsub.DetachSubscriptionRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[pubsub.DetachSubscriptionRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for detach_subscription + + Override in a subclass to manipulate the request or metadata + before they are sent to the Publisher server. + """ + return request, metadata + + def post_detach_subscription( + self, response: pubsub.DetachSubscriptionResponse + ) -> pubsub.DetachSubscriptionResponse: + """Post-rpc interceptor for detach_subscription + + Override in a subclass to manipulate the response + after it is returned by the Publisher server but before + it is returned to user code. + """ + return response + + def pre_get_topic( + self, request: pubsub.GetTopicRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[pubsub.GetTopicRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_topic + + Override in a subclass to manipulate the request or metadata + before they are sent to the Publisher server. + """ + return request, metadata + + def post_get_topic(self, response: pubsub.Topic) -> pubsub.Topic: + """Post-rpc interceptor for get_topic + + Override in a subclass to manipulate the response + after it is returned by the Publisher server but before + it is returned to user code. + """ + return response + + def pre_list_topics( + self, request: pubsub.ListTopicsRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[pubsub.ListTopicsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_topics + + Override in a subclass to manipulate the request or metadata + before they are sent to the Publisher server. + """ + return request, metadata + + def post_list_topics( + self, response: pubsub.ListTopicsResponse + ) -> pubsub.ListTopicsResponse: + """Post-rpc interceptor for list_topics + + Override in a subclass to manipulate the response + after it is returned by the Publisher server but before + it is returned to user code. + """ + return response + + def pre_list_topic_snapshots( + self, + request: pubsub.ListTopicSnapshotsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[pubsub.ListTopicSnapshotsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_topic_snapshots + + Override in a subclass to manipulate the request or metadata + before they are sent to the Publisher server. + """ + return request, metadata + + def post_list_topic_snapshots( + self, response: pubsub.ListTopicSnapshotsResponse + ) -> pubsub.ListTopicSnapshotsResponse: + """Post-rpc interceptor for list_topic_snapshots + + Override in a subclass to manipulate the response + after it is returned by the Publisher server but before + it is returned to user code. + """ + return response + + def pre_list_topic_subscriptions( + self, + request: pubsub.ListTopicSubscriptionsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[pubsub.ListTopicSubscriptionsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_topic_subscriptions + + Override in a subclass to manipulate the request or metadata + before they are sent to the Publisher server. + """ + return request, metadata + + def post_list_topic_subscriptions( + self, response: pubsub.ListTopicSubscriptionsResponse + ) -> pubsub.ListTopicSubscriptionsResponse: + """Post-rpc interceptor for list_topic_subscriptions + + Override in a subclass to manipulate the response + after it is returned by the Publisher server but before + it is returned to user code. + """ + return response + + def pre_publish( + self, request: pubsub.PublishRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[pubsub.PublishRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for publish + + Override in a subclass to manipulate the request or metadata + before they are sent to the Publisher server. + """ + return request, metadata + + def post_publish(self, response: pubsub.PublishResponse) -> pubsub.PublishResponse: + """Post-rpc interceptor for publish + + Override in a subclass to manipulate the response + after it is returned by the Publisher server but before + it is returned to user code. + """ + return response + + def pre_update_topic( + self, request: pubsub.UpdateTopicRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[pubsub.UpdateTopicRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_topic + + Override in a subclass to manipulate the request or metadata + before they are sent to the Publisher server. + """ + return request, metadata + + def post_update_topic(self, response: pubsub.Topic) -> pubsub.Topic: + """Post-rpc interceptor for update_topic + + Override in a subclass to manipulate the response + after it is returned by the Publisher server but before + it is returned to user code. + """ + return response + + def pre_get_iam_policy( + self, + request: iam_policy_pb2.GetIamPolicyRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[iam_policy_pb2.GetIamPolicyRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_iam_policy + + Override in a subclass to manipulate the request or metadata + before they are sent to the Publisher server. + """ + return request, metadata + + def post_get_iam_policy(self, response: policy_pb2.Policy) -> policy_pb2.Policy: + """Post-rpc interceptor for get_iam_policy + + Override in a subclass to manipulate the response + after it is returned by the Publisher server but before + it is returned to user code. + """ + return response + + def pre_set_iam_policy( + self, + request: iam_policy_pb2.SetIamPolicyRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[iam_policy_pb2.SetIamPolicyRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for set_iam_policy + + Override in a subclass to manipulate the request or metadata + before they are sent to the Publisher server. + """ + return request, metadata + + def post_set_iam_policy(self, response: policy_pb2.Policy) -> policy_pb2.Policy: + """Post-rpc interceptor for set_iam_policy + + Override in a subclass to manipulate the response + after it is returned by the Publisher server but before + it is returned to user code. + """ + return response + + def pre_test_iam_permissions( + self, + request: iam_policy_pb2.TestIamPermissionsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[iam_policy_pb2.TestIamPermissionsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for test_iam_permissions + + Override in a subclass to manipulate the request or metadata + before they are sent to the Publisher server. + """ + return request, metadata + + def post_test_iam_permissions( + self, response: iam_policy_pb2.TestIamPermissionsResponse + ) -> iam_policy_pb2.TestIamPermissionsResponse: + """Post-rpc interceptor for test_iam_permissions + + Override in a subclass to manipulate the response + after it is returned by the Publisher server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class PublisherRestStub: + _session: AuthorizedSession + _host: str + _interceptor: PublisherRestInterceptor + + +class PublisherRestTransport(PublisherTransport): + """REST backend transport for Publisher. + + The service that an application uses to manipulate topics, + and to send messages to a topic. + + 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 = "pubsub.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[PublisherRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` is provided. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + 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, + ) + 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 PublisherRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _CreateTopic(PublisherRestStub): + def __hash__(self): + return hash("CreateTopic") + + __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 + } + + def __call__( + self, + request: pubsub.Topic, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pubsub.Topic: + r"""Call the create topic method over HTTP. + + Args: + request (~.pubsub.Topic): + The request object. A topic resource. + 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, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pubsub.Topic: + A topic resource. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "put", + "uri": "/v1/{name=projects/*/topics/*}", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_create_topic(request, metadata) + pb_request = pubsub.Topic.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=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 = pubsub.Topic() + pb_resp = pubsub.Topic.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_topic(resp) + return resp + + class _DeleteTopic(PublisherRestStub): + def __hash__(self): + return hash("DeleteTopic") + + __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 + } + + def __call__( + self, + request: pubsub.DeleteTopicRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete topic method over HTTP. + + Args: + request (~.pubsub.DeleteTopicRequest): + The request object. Request for the ``DeleteTopic`` method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v1/{topic=projects/*/topics/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_topic(request, metadata) + pb_request = pubsub.DeleteTopicRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _DetachSubscription(PublisherRestStub): + def __hash__(self): + return hash("DetachSubscription") + + __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 + } + + def __call__( + self, + request: pubsub.DetachSubscriptionRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pubsub.DetachSubscriptionResponse: + r"""Call the detach subscription method over HTTP. + + Args: + request (~.pubsub.DetachSubscriptionRequest): + The request object. Request for the DetachSubscription + method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pubsub.DetachSubscriptionResponse: + Response for the DetachSubscription + method. Reserved for future use. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v1/{subscription=projects/*/subscriptions/*}:detach", + }, + ] + request, metadata = self._interceptor.pre_detach_subscription( + request, metadata + ) + pb_request = pubsub.DetachSubscriptionRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # 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 = pubsub.DetachSubscriptionResponse() + pb_resp = pubsub.DetachSubscriptionResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_detach_subscription(resp) + return resp + + class _GetTopic(PublisherRestStub): + def __hash__(self): + return hash("GetTopic") + + __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 + } + + def __call__( + self, + request: pubsub.GetTopicRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pubsub.Topic: + r"""Call the get topic method over HTTP. + + Args: + request (~.pubsub.GetTopicRequest): + The request object. Request for the GetTopic method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pubsub.Topic: + A topic resource. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v1/{topic=projects/*/topics/*}", + }, + ] + request, metadata = self._interceptor.pre_get_topic(request, metadata) + pb_request = pubsub.GetTopicRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # 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 = pubsub.Topic() + pb_resp = pubsub.Topic.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_topic(resp) + return resp + + class _ListTopics(PublisherRestStub): + def __hash__(self): + return hash("ListTopics") + + __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 + } + + def __call__( + self, + request: pubsub.ListTopicsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pubsub.ListTopicsResponse: + r"""Call the list topics method over HTTP. + + Args: + request (~.pubsub.ListTopicsRequest): + The request object. Request for the ``ListTopics`` method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pubsub.ListTopicsResponse: + Response for the ``ListTopics`` method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v1/{project=projects/*}/topics", + }, + ] + request, metadata = self._interceptor.pre_list_topics(request, metadata) + pb_request = pubsub.ListTopicsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # 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 = pubsub.ListTopicsResponse() + pb_resp = pubsub.ListTopicsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_topics(resp) + return resp + + class _ListTopicSnapshots(PublisherRestStub): + def __hash__(self): + return hash("ListTopicSnapshots") + + __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 + } + + def __call__( + self, + request: pubsub.ListTopicSnapshotsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pubsub.ListTopicSnapshotsResponse: + r"""Call the list topic snapshots method over HTTP. + + Args: + request (~.pubsub.ListTopicSnapshotsRequest): + The request object. Request for the ``ListTopicSnapshots`` method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pubsub.ListTopicSnapshotsResponse: + Response for the ``ListTopicSnapshots`` method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v1/{topic=projects/*/topics/*}/snapshots", + }, + ] + request, metadata = self._interceptor.pre_list_topic_snapshots( + request, metadata + ) + pb_request = pubsub.ListTopicSnapshotsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # 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 = pubsub.ListTopicSnapshotsResponse() + pb_resp = pubsub.ListTopicSnapshotsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_topic_snapshots(resp) + return resp + + class _ListTopicSubscriptions(PublisherRestStub): + def __hash__(self): + return hash("ListTopicSubscriptions") + + __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 + } + + def __call__( + self, + request: pubsub.ListTopicSubscriptionsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pubsub.ListTopicSubscriptionsResponse: + r"""Call the list topic subscriptions method over HTTP. + + Args: + request (~.pubsub.ListTopicSubscriptionsRequest): + The request object. Request for the ``ListTopicSubscriptions`` method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pubsub.ListTopicSubscriptionsResponse: + Response for the ``ListTopicSubscriptions`` method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v1/{topic=projects/*/topics/*}/subscriptions", + }, + ] + request, metadata = self._interceptor.pre_list_topic_subscriptions( + request, metadata + ) + pb_request = pubsub.ListTopicSubscriptionsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # 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 = pubsub.ListTopicSubscriptionsResponse() + pb_resp = pubsub.ListTopicSubscriptionsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_topic_subscriptions(resp) + return resp + + class _Publish(PublisherRestStub): + def __hash__(self): + return hash("Publish") + + __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 + } + + def __call__( + self, + request: pubsub.PublishRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pubsub.PublishResponse: + r"""Call the publish method over HTTP. + + Args: + request (~.pubsub.PublishRequest): + The request object. Request for the Publish method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pubsub.PublishResponse: + Response for the ``Publish`` method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v1/{topic=projects/*/topics/*}:publish", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_publish(request, metadata) + pb_request = pubsub.PublishRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=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 = pubsub.PublishResponse() + pb_resp = pubsub.PublishResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_publish(resp) + return resp + + class _UpdateTopic(PublisherRestStub): + def __hash__(self): + return hash("UpdateTopic") + + __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 + } + + def __call__( + self, + request: pubsub.UpdateTopicRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pubsub.Topic: + r"""Call the update topic method over HTTP. + + Args: + request (~.pubsub.UpdateTopicRequest): + The request object. Request for the UpdateTopic method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pubsub.Topic: + A topic resource. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v1/{topic.name=projects/*/topics/*}", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_update_topic(request, metadata) + pb_request = pubsub.UpdateTopicRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=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 = pubsub.Topic() + pb_resp = pubsub.Topic.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_topic(resp) + return resp + + @property + def create_topic(self) -> Callable[[pubsub.Topic], pubsub.Topic]: + # 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._CreateTopic(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_topic(self) -> Callable[[pubsub.DeleteTopicRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteTopic(self._session, self._host, self._interceptor) # type: ignore + + @property + def detach_subscription( + self, + ) -> Callable[ + [pubsub.DetachSubscriptionRequest], pubsub.DetachSubscriptionResponse + ]: + # 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._DetachSubscription(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_topic(self) -> Callable[[pubsub.GetTopicRequest], pubsub.Topic]: + # 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._GetTopic(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_topics( + self, + ) -> Callable[[pubsub.ListTopicsRequest], pubsub.ListTopicsResponse]: + # 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._ListTopics(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_topic_snapshots( + self, + ) -> Callable[ + [pubsub.ListTopicSnapshotsRequest], pubsub.ListTopicSnapshotsResponse + ]: + # 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._ListTopicSnapshots(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_topic_subscriptions( + self, + ) -> Callable[ + [pubsub.ListTopicSubscriptionsRequest], pubsub.ListTopicSubscriptionsResponse + ]: + # 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._ListTopicSubscriptions(self._session, self._host, self._interceptor) # type: ignore + + @property + def publish(self) -> Callable[[pubsub.PublishRequest], pubsub.PublishResponse]: + # 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._Publish(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_topic(self) -> Callable[[pubsub.UpdateTopicRequest], pubsub.Topic]: + # 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._UpdateTopic(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_iam_policy(self): + return self._GetIamPolicy(self._session, self._host, self._interceptor) # type: ignore + + class _GetIamPolicy(PublisherRestStub): + def __call__( + self, + request: iam_policy_pb2.GetIamPolicyRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> policy_pb2.Policy: + + r"""Call the get iam policy method over HTTP. + + Args: + request (iam_policy_pb2.GetIamPolicyRequest): + The request object for GetIamPolicy method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + policy_pb2.Policy: Response from GetIamPolicy method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v1/{resource=projects/*/topics/*}:getIamPolicy", + }, + { + "method": "get", + "uri": "/v1/{resource=projects/*/subscriptions/*}:getIamPolicy", + }, + { + "method": "get", + "uri": "/v1/{resource=projects/*/snapshots/*}:getIamPolicy", + }, + { + "method": "get", + "uri": "/v1/{resource=projects/*/schemas/*}:getIamPolicy", + }, + ] + + request, metadata = self._interceptor.pre_get_iam_policy(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = policy_pb2.Policy() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_iam_policy(resp) + return resp + + @property + def set_iam_policy(self): + return self._SetIamPolicy(self._session, self._host, self._interceptor) # type: ignore + + class _SetIamPolicy(PublisherRestStub): + def __call__( + self, + request: iam_policy_pb2.SetIamPolicyRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> policy_pb2.Policy: + + r"""Call the set iam policy method over HTTP. + + Args: + request (iam_policy_pb2.SetIamPolicyRequest): + The request object for SetIamPolicy method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + policy_pb2.Policy: Response from SetIamPolicy method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v1/{resource=projects/*/topics/*}:setIamPolicy", + "body": "*", + }, + { + "method": "post", + "uri": "/v1/{resource=projects/*/subscriptions/*}:setIamPolicy", + "body": "*", + }, + { + "method": "post", + "uri": "/v1/{resource=projects/*/snapshots/*}:setIamPolicy", + "body": "*", + }, + { + "method": "post", + "uri": "/v1/{resource=projects/*/schemas/*}:setIamPolicy", + "body": "*", + }, + ] + + request, metadata = self._interceptor.pre_set_iam_policy(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + body = json.loads(json.dumps(transcoded_request["body"])) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + data=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) + + resp = policy_pb2.Policy() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_set_iam_policy(resp) + return resp + + @property + def test_iam_permissions(self): + return self._TestIamPermissions(self._session, self._host, self._interceptor) # type: ignore + + class _TestIamPermissions(PublisherRestStub): + def __call__( + self, + request: iam_policy_pb2.TestIamPermissionsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> iam_policy_pb2.TestIamPermissionsResponse: + + r"""Call the test iam permissions method over HTTP. + + Args: + request (iam_policy_pb2.TestIamPermissionsRequest): + The request object for TestIamPermissions method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + iam_policy_pb2.TestIamPermissionsResponse: Response from TestIamPermissions method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v1/{resource=projects/*/subscriptions/*}:testIamPermissions", + "body": "*", + }, + { + "method": "post", + "uri": "/v1/{resource=projects/*/topics/*}:testIamPermissions", + "body": "*", + }, + { + "method": "post", + "uri": "/v1/{resource=projects/*/snapshots/*}:testIamPermissions", + "body": "*", + }, + { + "method": "post", + "uri": "/v1/{resource=projects/*/schemas/*}:testIamPermissions", + "body": "*", + }, + ] + + request, metadata = self._interceptor.pre_test_iam_permissions( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + body = json.loads(json.dumps(transcoded_request["body"])) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + data=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) + + resp = iam_policy_pb2.TestIamPermissionsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_test_iam_permissions(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("PublisherRestTransport",) diff --git a/google/pubsub_v1/services/schema_service/client.py b/google/pubsub_v1/services/schema_service/client.py index 2944caed9..fdec65ae1 100644 --- a/google/pubsub_v1/services/schema_service/client.py +++ b/google/pubsub_v1/services/schema_service/client.py @@ -58,6 +58,7 @@ from .transports.base import SchemaServiceTransport, DEFAULT_CLIENT_INFO from .transports.grpc import SchemaServiceGrpcTransport from .transports.grpc_asyncio import SchemaServiceGrpcAsyncIOTransport +from .transports.rest import SchemaServiceRestTransport class SchemaServiceClientMeta(type): @@ -71,6 +72,7 @@ class SchemaServiceClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[SchemaServiceTransport]] _transport_registry["grpc"] = SchemaServiceGrpcTransport _transport_registry["grpc_asyncio"] = SchemaServiceGrpcAsyncIOTransport + _transport_registry["rest"] = SchemaServiceRestTransport def get_transport_class( cls, diff --git a/google/pubsub_v1/services/schema_service/transports/__init__.py b/google/pubsub_v1/services/schema_service/transports/__init__.py index 6c2d9460a..fb62a346f 100644 --- a/google/pubsub_v1/services/schema_service/transports/__init__.py +++ b/google/pubsub_v1/services/schema_service/transports/__init__.py @@ -19,15 +19,20 @@ from .base import SchemaServiceTransport from .grpc import SchemaServiceGrpcTransport from .grpc_asyncio import SchemaServiceGrpcAsyncIOTransport +from .rest import SchemaServiceRestTransport +from .rest import SchemaServiceRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[SchemaServiceTransport]] _transport_registry["grpc"] = SchemaServiceGrpcTransport _transport_registry["grpc_asyncio"] = SchemaServiceGrpcAsyncIOTransport +_transport_registry["rest"] = SchemaServiceRestTransport __all__ = ( "SchemaServiceTransport", "SchemaServiceGrpcTransport", "SchemaServiceGrpcAsyncIOTransport", + "SchemaServiceRestTransport", + "SchemaServiceRestInterceptor", ) diff --git a/google/pubsub_v1/services/schema_service/transports/rest.py b/google/pubsub_v1/services/schema_service/transports/rest.py new file mode 100644 index 000000000..afa08f8ed --- /dev/null +++ b/google/pubsub_v1/services/schema_service/transports/rest.py @@ -0,0 +1,1763 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # 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 +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.iam.v1 import iam_policy_pb2 # type: ignore +from google.iam.v1 import policy_pb2 # type: ignore +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.iam.v1 import iam_policy_pb2 # type: ignore +from google.iam.v1 import policy_pb2 # type: ignore +from google.protobuf import empty_pb2 # type: ignore +from google.pubsub_v1.types import schema +from google.pubsub_v1.types import schema as gp_schema + +from .base import ( + SchemaServiceTransport, + DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO, +) + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class SchemaServiceRestInterceptor: + """Interceptor for SchemaService. + + 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 SchemaServiceRestTransport. + + .. code-block:: python + class MyCustomSchemaServiceInterceptor(SchemaServiceRestInterceptor): + def pre_commit_schema(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_commit_schema(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_create_schema(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_schema(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_schema(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_delete_schema_revision(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_delete_schema_revision(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_get_schema(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_schema(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_schema_revisions(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_schema_revisions(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_schemas(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_schemas(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_rollback_schema(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_rollback_schema(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_validate_message(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_validate_message(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_validate_schema(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_validate_schema(self, response): + logging.log(f"Received response: {response}") + return response + + transport = SchemaServiceRestTransport(interceptor=MyCustomSchemaServiceInterceptor()) + client = SchemaServiceClient(transport=transport) + + + """ + + def pre_commit_schema( + self, + request: gp_schema.CommitSchemaRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gp_schema.CommitSchemaRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for commit_schema + + Override in a subclass to manipulate the request or metadata + before they are sent to the SchemaService server. + """ + return request, metadata + + def post_commit_schema(self, response: gp_schema.Schema) -> gp_schema.Schema: + """Post-rpc interceptor for commit_schema + + Override in a subclass to manipulate the response + after it is returned by the SchemaService server but before + it is returned to user code. + """ + return response + + def pre_create_schema( + self, + request: gp_schema.CreateSchemaRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gp_schema.CreateSchemaRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_schema + + Override in a subclass to manipulate the request or metadata + before they are sent to the SchemaService server. + """ + return request, metadata + + def post_create_schema(self, response: gp_schema.Schema) -> gp_schema.Schema: + """Post-rpc interceptor for create_schema + + Override in a subclass to manipulate the response + after it is returned by the SchemaService server but before + it is returned to user code. + """ + return response + + def pre_delete_schema( + self, request: schema.DeleteSchemaRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[schema.DeleteSchemaRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_schema + + Override in a subclass to manipulate the request or metadata + before they are sent to the SchemaService server. + """ + return request, metadata + + def pre_delete_schema_revision( + self, + request: schema.DeleteSchemaRevisionRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[schema.DeleteSchemaRevisionRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_schema_revision + + Override in a subclass to manipulate the request or metadata + before they are sent to the SchemaService server. + """ + return request, metadata + + def post_delete_schema_revision(self, response: schema.Schema) -> schema.Schema: + """Post-rpc interceptor for delete_schema_revision + + Override in a subclass to manipulate the response + after it is returned by the SchemaService server but before + it is returned to user code. + """ + return response + + def pre_get_schema( + self, request: schema.GetSchemaRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[schema.GetSchemaRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_schema + + Override in a subclass to manipulate the request or metadata + before they are sent to the SchemaService server. + """ + return request, metadata + + def post_get_schema(self, response: schema.Schema) -> schema.Schema: + """Post-rpc interceptor for get_schema + + Override in a subclass to manipulate the response + after it is returned by the SchemaService server but before + it is returned to user code. + """ + return response + + def pre_list_schema_revisions( + self, + request: schema.ListSchemaRevisionsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[schema.ListSchemaRevisionsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_schema_revisions + + Override in a subclass to manipulate the request or metadata + before they are sent to the SchemaService server. + """ + return request, metadata + + def post_list_schema_revisions( + self, response: schema.ListSchemaRevisionsResponse + ) -> schema.ListSchemaRevisionsResponse: + """Post-rpc interceptor for list_schema_revisions + + Override in a subclass to manipulate the response + after it is returned by the SchemaService server but before + it is returned to user code. + """ + return response + + def pre_list_schemas( + self, request: schema.ListSchemasRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[schema.ListSchemasRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_schemas + + Override in a subclass to manipulate the request or metadata + before they are sent to the SchemaService server. + """ + return request, metadata + + def post_list_schemas( + self, response: schema.ListSchemasResponse + ) -> schema.ListSchemasResponse: + """Post-rpc interceptor for list_schemas + + Override in a subclass to manipulate the response + after it is returned by the SchemaService server but before + it is returned to user code. + """ + return response + + def pre_rollback_schema( + self, request: schema.RollbackSchemaRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[schema.RollbackSchemaRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for rollback_schema + + Override in a subclass to manipulate the request or metadata + before they are sent to the SchemaService server. + """ + return request, metadata + + def post_rollback_schema(self, response: schema.Schema) -> schema.Schema: + """Post-rpc interceptor for rollback_schema + + Override in a subclass to manipulate the response + after it is returned by the SchemaService server but before + it is returned to user code. + """ + return response + + def pre_validate_message( + self, + request: schema.ValidateMessageRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[schema.ValidateMessageRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for validate_message + + Override in a subclass to manipulate the request or metadata + before they are sent to the SchemaService server. + """ + return request, metadata + + def post_validate_message( + self, response: schema.ValidateMessageResponse + ) -> schema.ValidateMessageResponse: + """Post-rpc interceptor for validate_message + + Override in a subclass to manipulate the response + after it is returned by the SchemaService server but before + it is returned to user code. + """ + return response + + def pre_validate_schema( + self, + request: gp_schema.ValidateSchemaRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[gp_schema.ValidateSchemaRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for validate_schema + + Override in a subclass to manipulate the request or metadata + before they are sent to the SchemaService server. + """ + return request, metadata + + def post_validate_schema( + self, response: gp_schema.ValidateSchemaResponse + ) -> gp_schema.ValidateSchemaResponse: + """Post-rpc interceptor for validate_schema + + Override in a subclass to manipulate the response + after it is returned by the SchemaService server but before + it is returned to user code. + """ + return response + + def pre_get_iam_policy( + self, + request: iam_policy_pb2.GetIamPolicyRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[iam_policy_pb2.GetIamPolicyRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_iam_policy + + Override in a subclass to manipulate the request or metadata + before they are sent to the SchemaService server. + """ + return request, metadata + + def post_get_iam_policy(self, response: policy_pb2.Policy) -> policy_pb2.Policy: + """Post-rpc interceptor for get_iam_policy + + Override in a subclass to manipulate the response + after it is returned by the SchemaService server but before + it is returned to user code. + """ + return response + + def pre_set_iam_policy( + self, + request: iam_policy_pb2.SetIamPolicyRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[iam_policy_pb2.SetIamPolicyRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for set_iam_policy + + Override in a subclass to manipulate the request or metadata + before they are sent to the SchemaService server. + """ + return request, metadata + + def post_set_iam_policy(self, response: policy_pb2.Policy) -> policy_pb2.Policy: + """Post-rpc interceptor for set_iam_policy + + Override in a subclass to manipulate the response + after it is returned by the SchemaService server but before + it is returned to user code. + """ + return response + + def pre_test_iam_permissions( + self, + request: iam_policy_pb2.TestIamPermissionsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[iam_policy_pb2.TestIamPermissionsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for test_iam_permissions + + Override in a subclass to manipulate the request or metadata + before they are sent to the SchemaService server. + """ + return request, metadata + + def post_test_iam_permissions( + self, response: iam_policy_pb2.TestIamPermissionsResponse + ) -> iam_policy_pb2.TestIamPermissionsResponse: + """Post-rpc interceptor for test_iam_permissions + + Override in a subclass to manipulate the response + after it is returned by the SchemaService server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class SchemaServiceRestStub: + _session: AuthorizedSession + _host: str + _interceptor: SchemaServiceRestInterceptor + + +class SchemaServiceRestTransport(SchemaServiceTransport): + """REST backend transport for SchemaService. + + Service for doing schema-related operations. + + 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 = "pubsub.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[SchemaServiceRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` is provided. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + 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, + ) + 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 SchemaServiceRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _CommitSchema(SchemaServiceRestStub): + def __hash__(self): + return hash("CommitSchema") + + __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 + } + + def __call__( + self, + request: gp_schema.CommitSchemaRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gp_schema.Schema: + r"""Call the commit schema method over HTTP. + + Args: + request (~.gp_schema.CommitSchemaRequest): + The request object. Request for CommitSchema method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.gp_schema.Schema: + A schema resource. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v1/{name=projects/*/schemas/*}:commit", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_commit_schema(request, metadata) + pb_request = gp_schema.CommitSchemaRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=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 = gp_schema.Schema() + pb_resp = gp_schema.Schema.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_commit_schema(resp) + return resp + + class _CreateSchema(SchemaServiceRestStub): + def __hash__(self): + return hash("CreateSchema") + + __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 + } + + def __call__( + self, + request: gp_schema.CreateSchemaRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gp_schema.Schema: + r"""Call the create schema method over HTTP. + + Args: + request (~.gp_schema.CreateSchemaRequest): + The request object. Request for the CreateSchema method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.gp_schema.Schema: + A schema resource. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v1/{parent=projects/*}/schemas", + "body": "schema", + }, + ] + request, metadata = self._interceptor.pre_create_schema(request, metadata) + pb_request = gp_schema.CreateSchemaRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=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 = gp_schema.Schema() + pb_resp = gp_schema.Schema.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_schema(resp) + return resp + + class _DeleteSchema(SchemaServiceRestStub): + def __hash__(self): + return hash("DeleteSchema") + + __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 + } + + def __call__( + self, + request: schema.DeleteSchemaRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete schema method over HTTP. + + Args: + request (~.schema.DeleteSchemaRequest): + The request object. Request for the ``DeleteSchema`` method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v1/{name=projects/*/schemas/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_schema(request, metadata) + pb_request = schema.DeleteSchemaRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _DeleteSchemaRevision(SchemaServiceRestStub): + def __hash__(self): + return hash("DeleteSchemaRevision") + + __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 + } + + def __call__( + self, + request: schema.DeleteSchemaRevisionRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> schema.Schema: + r"""Call the delete schema revision method over HTTP. + + Args: + request (~.schema.DeleteSchemaRevisionRequest): + The request object. Request for the ``DeleteSchemaRevision`` method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.schema.Schema: + A schema resource. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v1/{name=projects/*/schemas/*}:deleteRevision", + }, + ] + request, metadata = self._interceptor.pre_delete_schema_revision( + request, metadata + ) + pb_request = schema.DeleteSchemaRevisionRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # 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 = schema.Schema() + pb_resp = schema.Schema.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_delete_schema_revision(resp) + return resp + + class _GetSchema(SchemaServiceRestStub): + def __hash__(self): + return hash("GetSchema") + + __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 + } + + def __call__( + self, + request: schema.GetSchemaRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> schema.Schema: + r"""Call the get schema method over HTTP. + + Args: + request (~.schema.GetSchemaRequest): + The request object. Request for the GetSchema method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.schema.Schema: + A schema resource. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v1/{name=projects/*/schemas/*}", + }, + ] + request, metadata = self._interceptor.pre_get_schema(request, metadata) + pb_request = schema.GetSchemaRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # 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 = schema.Schema() + pb_resp = schema.Schema.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_schema(resp) + return resp + + class _ListSchemaRevisions(SchemaServiceRestStub): + def __hash__(self): + return hash("ListSchemaRevisions") + + __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 + } + + def __call__( + self, + request: schema.ListSchemaRevisionsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> schema.ListSchemaRevisionsResponse: + r"""Call the list schema revisions method over HTTP. + + Args: + request (~.schema.ListSchemaRevisionsRequest): + The request object. Request for the ``ListSchemaRevisions`` method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.schema.ListSchemaRevisionsResponse: + Response for the ``ListSchemaRevisions`` method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v1/{name=projects/*/schemas/*}:listRevisions", + }, + ] + request, metadata = self._interceptor.pre_list_schema_revisions( + request, metadata + ) + pb_request = schema.ListSchemaRevisionsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # 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 = schema.ListSchemaRevisionsResponse() + pb_resp = schema.ListSchemaRevisionsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_schema_revisions(resp) + return resp + + class _ListSchemas(SchemaServiceRestStub): + def __hash__(self): + return hash("ListSchemas") + + __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 + } + + def __call__( + self, + request: schema.ListSchemasRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> schema.ListSchemasResponse: + r"""Call the list schemas method over HTTP. + + Args: + request (~.schema.ListSchemasRequest): + The request object. Request for the ``ListSchemas`` method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.schema.ListSchemasResponse: + Response for the ``ListSchemas`` method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v1/{parent=projects/*}/schemas", + }, + ] + request, metadata = self._interceptor.pre_list_schemas(request, metadata) + pb_request = schema.ListSchemasRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # 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 = schema.ListSchemasResponse() + pb_resp = schema.ListSchemasResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_schemas(resp) + return resp + + class _RollbackSchema(SchemaServiceRestStub): + def __hash__(self): + return hash("RollbackSchema") + + __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 + } + + def __call__( + self, + request: schema.RollbackSchemaRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> schema.Schema: + r"""Call the rollback schema method over HTTP. + + Args: + request (~.schema.RollbackSchemaRequest): + The request object. Request for the ``RollbackSchema`` method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.schema.Schema: + A schema resource. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v1/{name=projects/*/schemas/*}:rollback", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_rollback_schema(request, metadata) + pb_request = schema.RollbackSchemaRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=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 = schema.Schema() + pb_resp = schema.Schema.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_rollback_schema(resp) + return resp + + class _ValidateMessage(SchemaServiceRestStub): + def __hash__(self): + return hash("ValidateMessage") + + __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 + } + + def __call__( + self, + request: schema.ValidateMessageRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> schema.ValidateMessageResponse: + r"""Call the validate message method over HTTP. + + Args: + request (~.schema.ValidateMessageRequest): + The request object. Request for the ``ValidateMessage`` method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.schema.ValidateMessageResponse: + Response for the ``ValidateMessage`` method. Empty for + now. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v1/{parent=projects/*}/schemas:validateMessage", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_validate_message( + request, metadata + ) + pb_request = schema.ValidateMessageRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=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 = schema.ValidateMessageResponse() + pb_resp = schema.ValidateMessageResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_validate_message(resp) + return resp + + class _ValidateSchema(SchemaServiceRestStub): + def __hash__(self): + return hash("ValidateSchema") + + __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 + } + + def __call__( + self, + request: gp_schema.ValidateSchemaRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gp_schema.ValidateSchemaResponse: + r"""Call the validate schema method over HTTP. + + Args: + request (~.gp_schema.ValidateSchemaRequest): + The request object. Request for the ``ValidateSchema`` method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.gp_schema.ValidateSchemaResponse: + Response for the ``ValidateSchema`` method. Empty for + now. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v1/{parent=projects/*}/schemas:validate", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_validate_schema(request, metadata) + pb_request = gp_schema.ValidateSchemaRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=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 = gp_schema.ValidateSchemaResponse() + pb_resp = gp_schema.ValidateSchemaResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_validate_schema(resp) + return resp + + @property + def commit_schema( + self, + ) -> Callable[[gp_schema.CommitSchemaRequest], gp_schema.Schema]: + # 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._CommitSchema(self._session, self._host, self._interceptor) # type: ignore + + @property + def create_schema( + self, + ) -> Callable[[gp_schema.CreateSchemaRequest], gp_schema.Schema]: + # 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._CreateSchema(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_schema(self) -> Callable[[schema.DeleteSchemaRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteSchema(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_schema_revision( + self, + ) -> Callable[[schema.DeleteSchemaRevisionRequest], schema.Schema]: + # 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._DeleteSchemaRevision(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_schema(self) -> Callable[[schema.GetSchemaRequest], schema.Schema]: + # 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._GetSchema(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_schema_revisions( + self, + ) -> Callable[ + [schema.ListSchemaRevisionsRequest], schema.ListSchemaRevisionsResponse + ]: + # 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._ListSchemaRevisions(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_schemas( + self, + ) -> Callable[[schema.ListSchemasRequest], schema.ListSchemasResponse]: + # 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._ListSchemas(self._session, self._host, self._interceptor) # type: ignore + + @property + def rollback_schema( + self, + ) -> Callable[[schema.RollbackSchemaRequest], schema.Schema]: + # 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._RollbackSchema(self._session, self._host, self._interceptor) # type: ignore + + @property + def validate_message( + self, + ) -> Callable[[schema.ValidateMessageRequest], schema.ValidateMessageResponse]: + # 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._ValidateMessage(self._session, self._host, self._interceptor) # type: ignore + + @property + def validate_schema( + self, + ) -> Callable[[gp_schema.ValidateSchemaRequest], gp_schema.ValidateSchemaResponse]: + # 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._ValidateSchema(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_iam_policy(self): + return self._GetIamPolicy(self._session, self._host, self._interceptor) # type: ignore + + class _GetIamPolicy(SchemaServiceRestStub): + def __call__( + self, + request: iam_policy_pb2.GetIamPolicyRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> policy_pb2.Policy: + + r"""Call the get iam policy method over HTTP. + + Args: + request (iam_policy_pb2.GetIamPolicyRequest): + The request object for GetIamPolicy method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + policy_pb2.Policy: Response from GetIamPolicy method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v1/{resource=projects/*/topics/*}:getIamPolicy", + }, + { + "method": "get", + "uri": "/v1/{resource=projects/*/subscriptions/*}:getIamPolicy", + }, + { + "method": "get", + "uri": "/v1/{resource=projects/*/snapshots/*}:getIamPolicy", + }, + { + "method": "get", + "uri": "/v1/{resource=projects/*/schemas/*}:getIamPolicy", + }, + ] + + request, metadata = self._interceptor.pre_get_iam_policy(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = policy_pb2.Policy() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_iam_policy(resp) + return resp + + @property + def set_iam_policy(self): + return self._SetIamPolicy(self._session, self._host, self._interceptor) # type: ignore + + class _SetIamPolicy(SchemaServiceRestStub): + def __call__( + self, + request: iam_policy_pb2.SetIamPolicyRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> policy_pb2.Policy: + + r"""Call the set iam policy method over HTTP. + + Args: + request (iam_policy_pb2.SetIamPolicyRequest): + The request object for SetIamPolicy method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + policy_pb2.Policy: Response from SetIamPolicy method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v1/{resource=projects/*/topics/*}:setIamPolicy", + "body": "*", + }, + { + "method": "post", + "uri": "/v1/{resource=projects/*/subscriptions/*}:setIamPolicy", + "body": "*", + }, + { + "method": "post", + "uri": "/v1/{resource=projects/*/snapshots/*}:setIamPolicy", + "body": "*", + }, + { + "method": "post", + "uri": "/v1/{resource=projects/*/schemas/*}:setIamPolicy", + "body": "*", + }, + ] + + request, metadata = self._interceptor.pre_set_iam_policy(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + body = json.loads(json.dumps(transcoded_request["body"])) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + data=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) + + resp = policy_pb2.Policy() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_set_iam_policy(resp) + return resp + + @property + def test_iam_permissions(self): + return self._TestIamPermissions(self._session, self._host, self._interceptor) # type: ignore + + class _TestIamPermissions(SchemaServiceRestStub): + def __call__( + self, + request: iam_policy_pb2.TestIamPermissionsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> iam_policy_pb2.TestIamPermissionsResponse: + + r"""Call the test iam permissions method over HTTP. + + Args: + request (iam_policy_pb2.TestIamPermissionsRequest): + The request object for TestIamPermissions method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + iam_policy_pb2.TestIamPermissionsResponse: Response from TestIamPermissions method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v1/{resource=projects/*/subscriptions/*}:testIamPermissions", + "body": "*", + }, + { + "method": "post", + "uri": "/v1/{resource=projects/*/topics/*}:testIamPermissions", + "body": "*", + }, + { + "method": "post", + "uri": "/v1/{resource=projects/*/snapshots/*}:testIamPermissions", + "body": "*", + }, + { + "method": "post", + "uri": "/v1/{resource=projects/*/schemas/*}:testIamPermissions", + "body": "*", + }, + ] + + request, metadata = self._interceptor.pre_test_iam_permissions( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + body = json.loads(json.dumps(transcoded_request["body"])) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + data=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) + + resp = iam_policy_pb2.TestIamPermissionsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_test_iam_permissions(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("SchemaServiceRestTransport",) diff --git a/google/pubsub_v1/services/subscriber/client.py b/google/pubsub_v1/services/subscriber/client.py index a3237d122..a6518fff8 100644 --- a/google/pubsub_v1/services/subscriber/client.py +++ b/google/pubsub_v1/services/subscriber/client.py @@ -62,6 +62,7 @@ from .transports.base import SubscriberTransport, DEFAULT_CLIENT_INFO from .transports.grpc import SubscriberGrpcTransport from .transports.grpc_asyncio import SubscriberGrpcAsyncIOTransport +from .transports.rest import SubscriberRestTransport class SubscriberClientMeta(type): @@ -75,6 +76,7 @@ class SubscriberClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[SubscriberTransport]] _transport_registry["grpc"] = SubscriberGrpcTransport _transport_registry["grpc_asyncio"] = SubscriberGrpcAsyncIOTransport + _transport_registry["rest"] = SubscriberRestTransport def get_transport_class( cls, diff --git a/google/pubsub_v1/services/subscriber/transports/__init__.py b/google/pubsub_v1/services/subscriber/transports/__init__.py index f71cdecd4..bb13ec634 100644 --- a/google/pubsub_v1/services/subscriber/transports/__init__.py +++ b/google/pubsub_v1/services/subscriber/transports/__init__.py @@ -19,15 +19,20 @@ from .base import SubscriberTransport from .grpc import SubscriberGrpcTransport from .grpc_asyncio import SubscriberGrpcAsyncIOTransport +from .rest import SubscriberRestTransport +from .rest import SubscriberRestInterceptor # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[SubscriberTransport]] _transport_registry["grpc"] = SubscriberGrpcTransport _transport_registry["grpc_asyncio"] = SubscriberGrpcAsyncIOTransport +_transport_registry["rest"] = SubscriberRestTransport __all__ = ( "SubscriberTransport", "SubscriberGrpcTransport", "SubscriberGrpcAsyncIOTransport", + "SubscriberRestTransport", + "SubscriberRestInterceptor", ) diff --git a/google/pubsub_v1/services/subscriber/transports/rest.py b/google/pubsub_v1/services/subscriber/transports/rest.py new file mode 100644 index 000000000..c78fd7297 --- /dev/null +++ b/google/pubsub_v1/services/subscriber/transports/rest.py @@ -0,0 +1,2368 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from google.auth.transport.requests import AuthorizedSession # type: ignore +import json # type: ignore +import grpc # type: ignore +from google.auth.transport.grpc import SslCredentials # 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 +from google.api_core import rest_streaming +from google.api_core import path_template +from google.api_core import gapic_v1 + +from google.protobuf import json_format +from google.iam.v1 import iam_policy_pb2 # type: ignore +from google.iam.v1 import policy_pb2 # type: ignore +from requests import __version__ as requests_version +import dataclasses +import re +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +import warnings + +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore + + +from google.iam.v1 import iam_policy_pb2 # type: ignore +from google.iam.v1 import policy_pb2 # type: ignore +from google.protobuf import empty_pb2 # type: ignore +from google.pubsub_v1.types import pubsub + +from .base import SubscriberTransport, DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=requests_version, +) + + +class SubscriberRestInterceptor: + """Interceptor for Subscriber. + + 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 SubscriberRestTransport. + + .. code-block:: python + class MyCustomSubscriberInterceptor(SubscriberRestInterceptor): + def pre_acknowledge(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_create_snapshot(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_snapshot(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_create_subscription(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_create_subscription(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_delete_snapshot(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_delete_subscription(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_get_snapshot(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_snapshot(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_get_subscription(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_get_subscription(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_snapshots(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_snapshots(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_list_subscriptions(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_list_subscriptions(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_modify_ack_deadline(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_modify_push_config(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def pre_pull(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_pull(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_seek(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_seek(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_snapshot(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_snapshot(self, response): + logging.log(f"Received response: {response}") + return response + + def pre_update_subscription(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_update_subscription(self, response): + logging.log(f"Received response: {response}") + return response + + transport = SubscriberRestTransport(interceptor=MyCustomSubscriberInterceptor()) + client = SubscriberClient(transport=transport) + + + """ + + def pre_acknowledge( + self, request: pubsub.AcknowledgeRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[pubsub.AcknowledgeRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for acknowledge + + Override in a subclass to manipulate the request or metadata + before they are sent to the Subscriber server. + """ + return request, metadata + + def pre_create_snapshot( + self, request: pubsub.CreateSnapshotRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[pubsub.CreateSnapshotRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_snapshot + + Override in a subclass to manipulate the request or metadata + before they are sent to the Subscriber server. + """ + return request, metadata + + def post_create_snapshot(self, response: pubsub.Snapshot) -> pubsub.Snapshot: + """Post-rpc interceptor for create_snapshot + + Override in a subclass to manipulate the response + after it is returned by the Subscriber server but before + it is returned to user code. + """ + return response + + def pre_create_subscription( + self, request: pubsub.Subscription, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[pubsub.Subscription, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for create_subscription + + Override in a subclass to manipulate the request or metadata + before they are sent to the Subscriber server. + """ + return request, metadata + + def post_create_subscription( + self, response: pubsub.Subscription + ) -> pubsub.Subscription: + """Post-rpc interceptor for create_subscription + + Override in a subclass to manipulate the response + after it is returned by the Subscriber server but before + it is returned to user code. + """ + return response + + def pre_delete_snapshot( + self, request: pubsub.DeleteSnapshotRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[pubsub.DeleteSnapshotRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_snapshot + + Override in a subclass to manipulate the request or metadata + before they are sent to the Subscriber server. + """ + return request, metadata + + def pre_delete_subscription( + self, + request: pubsub.DeleteSubscriptionRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[pubsub.DeleteSubscriptionRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for delete_subscription + + Override in a subclass to manipulate the request or metadata + before they are sent to the Subscriber server. + """ + return request, metadata + + def pre_get_snapshot( + self, request: pubsub.GetSnapshotRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[pubsub.GetSnapshotRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_snapshot + + Override in a subclass to manipulate the request or metadata + before they are sent to the Subscriber server. + """ + return request, metadata + + def post_get_snapshot(self, response: pubsub.Snapshot) -> pubsub.Snapshot: + """Post-rpc interceptor for get_snapshot + + Override in a subclass to manipulate the response + after it is returned by the Subscriber server but before + it is returned to user code. + """ + return response + + def pre_get_subscription( + self, + request: pubsub.GetSubscriptionRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[pubsub.GetSubscriptionRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_subscription + + Override in a subclass to manipulate the request or metadata + before they are sent to the Subscriber server. + """ + return request, metadata + + def post_get_subscription( + self, response: pubsub.Subscription + ) -> pubsub.Subscription: + """Post-rpc interceptor for get_subscription + + Override in a subclass to manipulate the response + after it is returned by the Subscriber server but before + it is returned to user code. + """ + return response + + def pre_list_snapshots( + self, request: pubsub.ListSnapshotsRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[pubsub.ListSnapshotsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_snapshots + + Override in a subclass to manipulate the request or metadata + before they are sent to the Subscriber server. + """ + return request, metadata + + def post_list_snapshots( + self, response: pubsub.ListSnapshotsResponse + ) -> pubsub.ListSnapshotsResponse: + """Post-rpc interceptor for list_snapshots + + Override in a subclass to manipulate the response + after it is returned by the Subscriber server but before + it is returned to user code. + """ + return response + + def pre_list_subscriptions( + self, + request: pubsub.ListSubscriptionsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[pubsub.ListSubscriptionsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for list_subscriptions + + Override in a subclass to manipulate the request or metadata + before they are sent to the Subscriber server. + """ + return request, metadata + + def post_list_subscriptions( + self, response: pubsub.ListSubscriptionsResponse + ) -> pubsub.ListSubscriptionsResponse: + """Post-rpc interceptor for list_subscriptions + + Override in a subclass to manipulate the response + after it is returned by the Subscriber server but before + it is returned to user code. + """ + return response + + def pre_modify_ack_deadline( + self, + request: pubsub.ModifyAckDeadlineRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[pubsub.ModifyAckDeadlineRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for modify_ack_deadline + + Override in a subclass to manipulate the request or metadata + before they are sent to the Subscriber server. + """ + return request, metadata + + def pre_modify_push_config( + self, + request: pubsub.ModifyPushConfigRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[pubsub.ModifyPushConfigRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for modify_push_config + + Override in a subclass to manipulate the request or metadata + before they are sent to the Subscriber server. + """ + return request, metadata + + def pre_pull( + self, request: pubsub.PullRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[pubsub.PullRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for pull + + Override in a subclass to manipulate the request or metadata + before they are sent to the Subscriber server. + """ + return request, metadata + + def post_pull(self, response: pubsub.PullResponse) -> pubsub.PullResponse: + """Post-rpc interceptor for pull + + Override in a subclass to manipulate the response + after it is returned by the Subscriber server but before + it is returned to user code. + """ + return response + + def pre_seek( + self, request: pubsub.SeekRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[pubsub.SeekRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for seek + + Override in a subclass to manipulate the request or metadata + before they are sent to the Subscriber server. + """ + return request, metadata + + def post_seek(self, response: pubsub.SeekResponse) -> pubsub.SeekResponse: + """Post-rpc interceptor for seek + + Override in a subclass to manipulate the response + after it is returned by the Subscriber server but before + it is returned to user code. + """ + return response + + def pre_update_snapshot( + self, request: pubsub.UpdateSnapshotRequest, metadata: Sequence[Tuple[str, str]] + ) -> Tuple[pubsub.UpdateSnapshotRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_snapshot + + Override in a subclass to manipulate the request or metadata + before they are sent to the Subscriber server. + """ + return request, metadata + + def post_update_snapshot(self, response: pubsub.Snapshot) -> pubsub.Snapshot: + """Post-rpc interceptor for update_snapshot + + Override in a subclass to manipulate the response + after it is returned by the Subscriber server but before + it is returned to user code. + """ + return response + + def pre_update_subscription( + self, + request: pubsub.UpdateSubscriptionRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[pubsub.UpdateSubscriptionRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for update_subscription + + Override in a subclass to manipulate the request or metadata + before they are sent to the Subscriber server. + """ + return request, metadata + + def post_update_subscription( + self, response: pubsub.Subscription + ) -> pubsub.Subscription: + """Post-rpc interceptor for update_subscription + + Override in a subclass to manipulate the response + after it is returned by the Subscriber server but before + it is returned to user code. + """ + return response + + def pre_get_iam_policy( + self, + request: iam_policy_pb2.GetIamPolicyRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[iam_policy_pb2.GetIamPolicyRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for get_iam_policy + + Override in a subclass to manipulate the request or metadata + before they are sent to the Subscriber server. + """ + return request, metadata + + def post_get_iam_policy(self, response: policy_pb2.Policy) -> policy_pb2.Policy: + """Post-rpc interceptor for get_iam_policy + + Override in a subclass to manipulate the response + after it is returned by the Subscriber server but before + it is returned to user code. + """ + return response + + def pre_set_iam_policy( + self, + request: iam_policy_pb2.SetIamPolicyRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[iam_policy_pb2.SetIamPolicyRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for set_iam_policy + + Override in a subclass to manipulate the request or metadata + before they are sent to the Subscriber server. + """ + return request, metadata + + def post_set_iam_policy(self, response: policy_pb2.Policy) -> policy_pb2.Policy: + """Post-rpc interceptor for set_iam_policy + + Override in a subclass to manipulate the response + after it is returned by the Subscriber server but before + it is returned to user code. + """ + return response + + def pre_test_iam_permissions( + self, + request: iam_policy_pb2.TestIamPermissionsRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[iam_policy_pb2.TestIamPermissionsRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for test_iam_permissions + + Override in a subclass to manipulate the request or metadata + before they are sent to the Subscriber server. + """ + return request, metadata + + def post_test_iam_permissions( + self, response: iam_policy_pb2.TestIamPermissionsResponse + ) -> iam_policy_pb2.TestIamPermissionsResponse: + """Post-rpc interceptor for test_iam_permissions + + Override in a subclass to manipulate the response + after it is returned by the Subscriber server but before + it is returned to user code. + """ + return response + + +@dataclasses.dataclass +class SubscriberRestStub: + _session: AuthorizedSession + _host: str + _interceptor: SubscriberRestInterceptor + + +class SubscriberRestTransport(SubscriberTransport): + """REST backend transport for Subscriber. + + The service that an application uses to manipulate subscriptions and + to consume messages from a subscription via the ``Pull`` method or + by establishing a bi-directional stream using the ``StreamingPull`` + method. + + 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 = "pubsub.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[SubscriberRestInterceptor] = None, + api_audience: Optional[str] = None, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): + The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client + certificate to configure mutual TLS HTTP channel. It is ignored + if ``channel`` is provided. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be used for service account credentials. + url_scheme: the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc. + # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the + # credentials object + 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, + ) + 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 SubscriberRestInterceptor() + self._prep_wrapped_messages(client_info) + + class _Acknowledge(SubscriberRestStub): + def __hash__(self): + return hash("Acknowledge") + + __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 + } + + def __call__( + self, + request: pubsub.AcknowledgeRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the acknowledge method over HTTP. + + Args: + request (~.pubsub.AcknowledgeRequest): + The request object. Request for the Acknowledge method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v1/{subscription=projects/*/subscriptions/*}:acknowledge", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_acknowledge(request, metadata) + pb_request = pubsub.AcknowledgeRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=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) + + class _CreateSnapshot(SubscriberRestStub): + def __hash__(self): + return hash("CreateSnapshot") + + __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 + } + + def __call__( + self, + request: pubsub.CreateSnapshotRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pubsub.Snapshot: + r"""Call the create snapshot method over HTTP. + + Args: + request (~.pubsub.CreateSnapshotRequest): + The request object. Request for the ``CreateSnapshot`` method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pubsub.Snapshot: + A snapshot resource. Snapshots are used in + `Seek `__ + operations, which allow you to manage message + acknowledgments in bulk. That is, you can set the + acknowledgment state of messages in an existing + subscription to the state captured by a snapshot. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "put", + "uri": "/v1/{name=projects/*/snapshots/*}", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_create_snapshot(request, metadata) + pb_request = pubsub.CreateSnapshotRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=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 = pubsub.Snapshot() + pb_resp = pubsub.Snapshot.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_snapshot(resp) + return resp + + class _CreateSubscription(SubscriberRestStub): + def __hash__(self): + return hash("CreateSubscription") + + __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 + } + + def __call__( + self, + request: pubsub.Subscription, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pubsub.Subscription: + r"""Call the create subscription method over HTTP. + + Args: + request (~.pubsub.Subscription): + The request object. A subscription resource. If none of ``push_config`` or + ``bigquery_config`` is set, then the subscriber will + pull and ack messages using API methods. At most one of + these fields may 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, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pubsub.Subscription: + A subscription resource. If none of ``push_config`` or + ``bigquery_config`` is set, then the subscriber will + pull and ack messages using API methods. At most one of + these fields may be set. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "put", + "uri": "/v1/{name=projects/*/subscriptions/*}", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_create_subscription( + request, metadata + ) + pb_request = pubsub.Subscription.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=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 = pubsub.Subscription() + pb_resp = pubsub.Subscription.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_create_subscription(resp) + return resp + + class _DeleteSnapshot(SubscriberRestStub): + def __hash__(self): + return hash("DeleteSnapshot") + + __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 + } + + def __call__( + self, + request: pubsub.DeleteSnapshotRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete snapshot method over HTTP. + + Args: + request (~.pubsub.DeleteSnapshotRequest): + The request object. Request for the ``DeleteSnapshot`` method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v1/{snapshot=projects/*/snapshots/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_snapshot(request, metadata) + pb_request = pubsub.DeleteSnapshotRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _DeleteSubscription(SubscriberRestStub): + def __hash__(self): + return hash("DeleteSubscription") + + __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 + } + + def __call__( + self, + request: pubsub.DeleteSubscriptionRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the delete subscription method over HTTP. + + Args: + request (~.pubsub.DeleteSubscriptionRequest): + The request object. Request for the DeleteSubscription + method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "delete", + "uri": "/v1/{subscription=projects/*/subscriptions/*}", + }, + ] + request, metadata = self._interceptor.pre_delete_subscription( + request, metadata + ) + pb_request = pubsub.DeleteSubscriptionRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + class _GetSnapshot(SubscriberRestStub): + def __hash__(self): + return hash("GetSnapshot") + + __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 + } + + def __call__( + self, + request: pubsub.GetSnapshotRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pubsub.Snapshot: + r"""Call the get snapshot method over HTTP. + + Args: + request (~.pubsub.GetSnapshotRequest): + The request object. Request for the GetSnapshot method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pubsub.Snapshot: + A snapshot resource. Snapshots are used in + `Seek `__ + operations, which allow you to manage message + acknowledgments in bulk. That is, you can set the + acknowledgment state of messages in an existing + subscription to the state captured by a snapshot. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v1/{snapshot=projects/*/snapshots/*}", + }, + ] + request, metadata = self._interceptor.pre_get_snapshot(request, metadata) + pb_request = pubsub.GetSnapshotRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # 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 = pubsub.Snapshot() + pb_resp = pubsub.Snapshot.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_snapshot(resp) + return resp + + class _GetSubscription(SubscriberRestStub): + def __hash__(self): + return hash("GetSubscription") + + __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 + } + + def __call__( + self, + request: pubsub.GetSubscriptionRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pubsub.Subscription: + r"""Call the get subscription method over HTTP. + + Args: + request (~.pubsub.GetSubscriptionRequest): + The request object. Request for the GetSubscription + method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pubsub.Subscription: + A subscription resource. If none of ``push_config`` or + ``bigquery_config`` is set, then the subscriber will + pull and ack messages using API methods. At most one of + these fields may be set. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v1/{subscription=projects/*/subscriptions/*}", + }, + ] + request, metadata = self._interceptor.pre_get_subscription( + request, metadata + ) + pb_request = pubsub.GetSubscriptionRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # 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 = pubsub.Subscription() + pb_resp = pubsub.Subscription.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_get_subscription(resp) + return resp + + class _ListSnapshots(SubscriberRestStub): + def __hash__(self): + return hash("ListSnapshots") + + __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 + } + + def __call__( + self, + request: pubsub.ListSnapshotsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pubsub.ListSnapshotsResponse: + r"""Call the list snapshots method over HTTP. + + Args: + request (~.pubsub.ListSnapshotsRequest): + The request object. Request for the ``ListSnapshots`` method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pubsub.ListSnapshotsResponse: + Response for the ``ListSnapshots`` method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v1/{project=projects/*}/snapshots", + }, + ] + request, metadata = self._interceptor.pre_list_snapshots(request, metadata) + pb_request = pubsub.ListSnapshotsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # 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 = pubsub.ListSnapshotsResponse() + pb_resp = pubsub.ListSnapshotsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_snapshots(resp) + return resp + + class _ListSubscriptions(SubscriberRestStub): + def __hash__(self): + return hash("ListSubscriptions") + + __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 + } + + def __call__( + self, + request: pubsub.ListSubscriptionsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pubsub.ListSubscriptionsResponse: + r"""Call the list subscriptions method over HTTP. + + Args: + request (~.pubsub.ListSubscriptionsRequest): + The request object. Request for the ``ListSubscriptions`` method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pubsub.ListSubscriptionsResponse: + Response for the ``ListSubscriptions`` method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v1/{project=projects/*}/subscriptions", + }, + ] + request, metadata = self._interceptor.pre_list_subscriptions( + request, metadata + ) + pb_request = pubsub.ListSubscriptionsRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + ) + + # 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 = pubsub.ListSubscriptionsResponse() + pb_resp = pubsub.ListSubscriptionsResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_list_subscriptions(resp) + return resp + + class _ModifyAckDeadline(SubscriberRestStub): + def __hash__(self): + return hash("ModifyAckDeadline") + + __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 + } + + def __call__( + self, + request: pubsub.ModifyAckDeadlineRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the modify ack deadline method over HTTP. + + Args: + request (~.pubsub.ModifyAckDeadlineRequest): + The request object. Request for the ModifyAckDeadline + method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v1/{subscription=projects/*/subscriptions/*}:modifyAckDeadline", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_modify_ack_deadline( + request, metadata + ) + pb_request = pubsub.ModifyAckDeadlineRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=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) + + class _ModifyPushConfig(SubscriberRestStub): + def __hash__(self): + return hash("ModifyPushConfig") + + __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 + } + + def __call__( + self, + request: pubsub.ModifyPushConfigRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ): + r"""Call the modify push config method over HTTP. + + Args: + request (~.pubsub.ModifyPushConfigRequest): + The request object. Request for the ModifyPushConfig + method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v1/{subscription=projects/*/subscriptions/*}:modifyPushConfig", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_modify_push_config( + request, metadata + ) + pb_request = pubsub.ModifyPushConfigRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=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) + + class _Pull(SubscriberRestStub): + def __hash__(self): + return hash("Pull") + + __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 + } + + def __call__( + self, + request: pubsub.PullRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pubsub.PullResponse: + r"""Call the pull method over HTTP. + + Args: + request (~.pubsub.PullRequest): + The request object. Request for the ``Pull`` method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pubsub.PullResponse: + Response for the ``Pull`` method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v1/{subscription=projects/*/subscriptions/*}:pull", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_pull(request, metadata) + pb_request = pubsub.PullRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=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 = pubsub.PullResponse() + pb_resp = pubsub.PullResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_pull(resp) + return resp + + class _Seek(SubscriberRestStub): + def __hash__(self): + return hash("Seek") + + __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 + } + + def __call__( + self, + request: pubsub.SeekRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pubsub.SeekResponse: + r"""Call the seek method over HTTP. + + Args: + request (~.pubsub.SeekRequest): + The request object. Request for the ``Seek`` method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pubsub.SeekResponse: + Response for the ``Seek`` method (this response is + empty). + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v1/{subscription=projects/*/subscriptions/*}:seek", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_seek(request, metadata) + pb_request = pubsub.SeekRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=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 = pubsub.SeekResponse() + pb_resp = pubsub.SeekResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_seek(resp) + return resp + + class _StreamingPull(SubscriberRestStub): + def __hash__(self): + return hash("StreamingPull") + + def __call__( + self, + request: pubsub.StreamingPullRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> rest_streaming.ResponseIterator: + raise NotImplementedError( + "Method StreamingPull is not available over REST transport" + ) + + class _UpdateSnapshot(SubscriberRestStub): + def __hash__(self): + return hash("UpdateSnapshot") + + __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 + } + + def __call__( + self, + request: pubsub.UpdateSnapshotRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pubsub.Snapshot: + r"""Call the update snapshot method over HTTP. + + Args: + request (~.pubsub.UpdateSnapshotRequest): + The request object. Request for the UpdateSnapshot + method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pubsub.Snapshot: + A snapshot resource. Snapshots are used in + `Seek `__ + operations, which allow you to manage message + acknowledgments in bulk. That is, you can set the + acknowledgment state of messages in an existing + subscription to the state captured by a snapshot. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v1/{snapshot.name=projects/*/snapshots/*}", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_update_snapshot(request, metadata) + pb_request = pubsub.UpdateSnapshotRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=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 = pubsub.Snapshot() + pb_resp = pubsub.Snapshot.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_snapshot(resp) + return resp + + class _UpdateSubscription(SubscriberRestStub): + def __hash__(self): + return hash("UpdateSubscription") + + __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 + } + + def __call__( + self, + request: pubsub.UpdateSubscriptionRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pubsub.Subscription: + r"""Call the update subscription method over HTTP. + + Args: + request (~.pubsub.UpdateSubscriptionRequest): + The request object. Request for the UpdateSubscription + method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pubsub.Subscription: + A subscription resource. If none of ``push_config`` or + ``bigquery_config`` is set, then the subscriber will + pull and ack messages using API methods. At most one of + these fields may be set. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "patch", + "uri": "/v1/{subscription.name=projects/*/subscriptions/*}", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_update_subscription( + request, metadata + ) + pb_request = pubsub.UpdateSubscriptionRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=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 = pubsub.Subscription() + pb_resp = pubsub.Subscription.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_update_subscription(resp) + return resp + + @property + def acknowledge(self) -> Callable[[pubsub.AcknowledgeRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._Acknowledge(self._session, self._host, self._interceptor) # type: ignore + + @property + def create_snapshot( + self, + ) -> Callable[[pubsub.CreateSnapshotRequest], pubsub.Snapshot]: + # 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._CreateSnapshot(self._session, self._host, self._interceptor) # type: ignore + + @property + def create_subscription( + self, + ) -> Callable[[pubsub.Subscription], pubsub.Subscription]: + # 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._CreateSubscription(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_snapshot( + self, + ) -> Callable[[pubsub.DeleteSnapshotRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteSnapshot(self._session, self._host, self._interceptor) # type: ignore + + @property + def delete_subscription( + self, + ) -> Callable[[pubsub.DeleteSubscriptionRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._DeleteSubscription(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_snapshot(self) -> Callable[[pubsub.GetSnapshotRequest], pubsub.Snapshot]: + # 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._GetSnapshot(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_subscription( + self, + ) -> Callable[[pubsub.GetSubscriptionRequest], pubsub.Subscription]: + # 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._GetSubscription(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_snapshots( + self, + ) -> Callable[[pubsub.ListSnapshotsRequest], pubsub.ListSnapshotsResponse]: + # 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._ListSnapshots(self._session, self._host, self._interceptor) # type: ignore + + @property + def list_subscriptions( + self, + ) -> Callable[[pubsub.ListSubscriptionsRequest], pubsub.ListSubscriptionsResponse]: + # 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._ListSubscriptions(self._session, self._host, self._interceptor) # type: ignore + + @property + def modify_ack_deadline( + self, + ) -> Callable[[pubsub.ModifyAckDeadlineRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ModifyAckDeadline(self._session, self._host, self._interceptor) # type: ignore + + @property + def modify_push_config( + self, + ) -> Callable[[pubsub.ModifyPushConfigRequest], empty_pb2.Empty]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._ModifyPushConfig(self._session, self._host, self._interceptor) # type: ignore + + @property + def pull(self) -> Callable[[pubsub.PullRequest], pubsub.PullResponse]: + # 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._Pull(self._session, self._host, self._interceptor) # type: ignore + + @property + def seek(self) -> Callable[[pubsub.SeekRequest], pubsub.SeekResponse]: + # 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._Seek(self._session, self._host, self._interceptor) # type: ignore + + @property + def streaming_pull( + self, + ) -> Callable[[pubsub.StreamingPullRequest], pubsub.StreamingPullResponse]: + # 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._StreamingPull(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_snapshot( + self, + ) -> Callable[[pubsub.UpdateSnapshotRequest], pubsub.Snapshot]: + # 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._UpdateSnapshot(self._session, self._host, self._interceptor) # type: ignore + + @property + def update_subscription( + self, + ) -> Callable[[pubsub.UpdateSubscriptionRequest], pubsub.Subscription]: + # 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._UpdateSubscription(self._session, self._host, self._interceptor) # type: ignore + + @property + def get_iam_policy(self): + return self._GetIamPolicy(self._session, self._host, self._interceptor) # type: ignore + + class _GetIamPolicy(SubscriberRestStub): + def __call__( + self, + request: iam_policy_pb2.GetIamPolicyRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> policy_pb2.Policy: + + r"""Call the get iam policy method over HTTP. + + Args: + request (iam_policy_pb2.GetIamPolicyRequest): + The request object for GetIamPolicy method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + policy_pb2.Policy: Response from GetIamPolicy method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "get", + "uri": "/v1/{resource=projects/*/topics/*}:getIamPolicy", + }, + { + "method": "get", + "uri": "/v1/{resource=projects/*/subscriptions/*}:getIamPolicy", + }, + { + "method": "get", + "uri": "/v1/{resource=projects/*/snapshots/*}:getIamPolicy", + }, + { + "method": "get", + "uri": "/v1/{resource=projects/*/schemas/*}:getIamPolicy", + }, + ] + + request, metadata = self._interceptor.pre_get_iam_policy(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + resp = policy_pb2.Policy() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_get_iam_policy(resp) + return resp + + @property + def set_iam_policy(self): + return self._SetIamPolicy(self._session, self._host, self._interceptor) # type: ignore + + class _SetIamPolicy(SubscriberRestStub): + def __call__( + self, + request: iam_policy_pb2.SetIamPolicyRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> policy_pb2.Policy: + + r"""Call the set iam policy method over HTTP. + + Args: + request (iam_policy_pb2.SetIamPolicyRequest): + The request object for SetIamPolicy method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + policy_pb2.Policy: Response from SetIamPolicy method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v1/{resource=projects/*/topics/*}:setIamPolicy", + "body": "*", + }, + { + "method": "post", + "uri": "/v1/{resource=projects/*/subscriptions/*}:setIamPolicy", + "body": "*", + }, + { + "method": "post", + "uri": "/v1/{resource=projects/*/snapshots/*}:setIamPolicy", + "body": "*", + }, + { + "method": "post", + "uri": "/v1/{resource=projects/*/schemas/*}:setIamPolicy", + "body": "*", + }, + ] + + request, metadata = self._interceptor.pre_set_iam_policy(request, metadata) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + body = json.loads(json.dumps(transcoded_request["body"])) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + data=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) + + resp = policy_pb2.Policy() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_set_iam_policy(resp) + return resp + + @property + def test_iam_permissions(self): + return self._TestIamPermissions(self._session, self._host, self._interceptor) # type: ignore + + class _TestIamPermissions(SubscriberRestStub): + def __call__( + self, + request: iam_policy_pb2.TestIamPermissionsRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> iam_policy_pb2.TestIamPermissionsResponse: + + r"""Call the test iam permissions method over HTTP. + + Args: + request (iam_policy_pb2.TestIamPermissionsRequest): + The request object for TestIamPermissions method. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + iam_policy_pb2.TestIamPermissionsResponse: Response from TestIamPermissions method. + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v1/{resource=projects/*/subscriptions/*}:testIamPermissions", + "body": "*", + }, + { + "method": "post", + "uri": "/v1/{resource=projects/*/topics/*}:testIamPermissions", + "body": "*", + }, + { + "method": "post", + "uri": "/v1/{resource=projects/*/snapshots/*}:testIamPermissions", + "body": "*", + }, + { + "method": "post", + "uri": "/v1/{resource=projects/*/schemas/*}:testIamPermissions", + "body": "*", + }, + ] + + request, metadata = self._interceptor.pre_test_iam_permissions( + request, metadata + ) + request_kwargs = json_format.MessageToDict(request) + transcoded_request = path_template.transcode(http_options, **request_kwargs) + + body = json.loads(json.dumps(transcoded_request["body"])) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads(json.dumps(transcoded_request["query_params"])) + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params), + data=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) + + resp = iam_policy_pb2.TestIamPermissionsResponse() + resp = json_format.Parse(response.content.decode("utf-8"), resp) + resp = self._interceptor.post_test_iam_permissions(resp) + return resp + + @property + def kind(self) -> str: + return "rest" + + def close(self): + self._session.close() + + +__all__ = ("SubscriberRestTransport",) diff --git a/google/pubsub_v1/types/pubsub.py b/google/pubsub_v1/types/pubsub.py index cee8cee4c..df299cc8b 100644 --- a/google/pubsub_v1/types/pubsub.py +++ b/google/pubsub_v1/types/pubsub.py @@ -264,7 +264,9 @@ class PubsubMessage(proto.Message): will be delivered to subscribers in the order in which they are received by the Pub/Sub system. All ``PubsubMessage``\ s published in a given ``PublishRequest`` must specify the - same ``ordering_key`` value. + same ``ordering_key`` value. For more information, see + `ordering + messages `__. """ data: bytes = proto.Field( @@ -667,7 +669,9 @@ class Subscription(proto.Message): operations on the subscription. If ``expiration_policy`` is not set, a *default policy* with ``ttl`` of 31 days will be used. The minimum allowed value for - ``expiration_policy.ttl`` is 1 day. + ``expiration_policy.ttl`` is 1 day. If ``expiration_policy`` + is set, but ``expiration_policy.ttl`` is not set, the + subscription never expires. filter (str): An expression written in the Pub/Sub `filter language `__. diff --git a/tests/system.py b/tests/system.py index bb1265453..9a216c48f 100644 --- a/tests/system.py +++ b/tests/system.py @@ -55,12 +55,12 @@ def project(): yield default_project -@pytest.fixture(params=["grpc"]) +@pytest.fixture(params=["grpc", "rest"]) def publisher(request): yield pubsub_v1.PublisherClient(transport=request.param) -@pytest.fixture(params=["grpc"]) +@pytest.fixture(params=["grpc", "rest"]) def subscriber(request): yield pubsub_v1.SubscriberClient(transport=request.param) @@ -107,7 +107,8 @@ def test_publish_messages(publisher, topic_path_base, cleanup): assert isinstance(result, str) -def test_publish_large_messages(publisher, topic_path_base, cleanup): +def test_publish_large_messages(topic_path_base, cleanup): + publisher = pubsub_v1.PublisherClient(transport="grpc") # Customize topic path to test. topic_path = topic_path_base + "-publish-large-messages" # Make sure the topic gets deleted. @@ -139,8 +140,9 @@ def test_publish_large_messages(publisher, topic_path_base, cleanup): def test_subscribe_to_messages( - publisher, topic_path_base, subscriber, subscription_path_base, cleanup + publisher, topic_path_base, subscription_path_base, cleanup ): + subscriber = pubsub_v1.SubscriberClient(transport="grpc") # Customize topic path to test. topic_path = topic_path_base + "-subscribe-to-messages" subscription_path = subscription_path_base + "-subscribe-to-messages" @@ -187,8 +189,9 @@ def test_subscribe_to_messages( def test_subscribe_to_messages_async_callbacks( - publisher, topic_path_base, subscriber, subscription_path_base, cleanup + publisher, topic_path_base, subscription_path_base, cleanup ): + subscriber = pubsub_v1.SubscriberClient(transport="grpc") # Customize topic path to test. custom_str = "-subscribe-to-messages-async-callback" topic_path = topic_path_base + custom_str @@ -366,10 +369,10 @@ def test_listing_topic_subscriptions(publisher, subscriber, project, cleanup): assert subscriptions == {subscription_paths[0], subscription_paths[2]} -def test_managing_topic_iam_policy(publisher, topic_path_base, cleanup): +def test_managing_topic_iam_policy(topic_path_base, cleanup): + publisher = pubsub_v1.PublisherClient(transport="grpc") topic_path = topic_path_base + "-managing-topic-iam-policy" cleanup.append((publisher.delete_topic, (), {"topic": topic_path})) - # create a topic and customize its policy publisher.create_topic(name=topic_path) topic_policy = publisher.get_iam_policy(request={"resource": topic_path}) @@ -396,8 +399,9 @@ def test_managing_topic_iam_policy(publisher, topic_path_base, cleanup): def test_managing_subscription_iam_policy( - publisher, subscriber, topic_path_base, subscription_path_base, cleanup + publisher, topic_path_base, subscription_path_base, cleanup ): + subscriber = pubsub_v1.SubscriberClient(transport="grpc") custom_str = "-managing-subscription-iam-policy" topic_path = topic_path_base + custom_str subscription_path = subscription_path_base + custom_str @@ -433,8 +437,9 @@ def test_managing_subscription_iam_policy( assert bindings[1].members == ["group:cloud-logs@google.com"] +@pytest.mark.parametrize("transport", ["grpc", "rest"]) def test_subscriber_not_leaking_open_sockets( - publisher, topic_path_base, subscription_path_base, cleanup + publisher, topic_path_base, subscription_path_base, cleanup, transport ): # Make sure the topic and the supscription get deleted. # NOTE: Since subscriber client will be closed in the test, we should not @@ -516,8 +521,9 @@ def test_synchronous_pull_no_deadline_error_if_no_messages( class TestStreamingPull(object): def test_streaming_pull_callback_error_propagation( - self, publisher, topic_path_base, subscriber, subscription_path_base, cleanup + self, publisher, topic_path_base, subscription_path_base, cleanup ): + subscriber = pubsub_v1.SubscriberClient(transport="grpc") custom_str = "-streaming-pull-callback-error-propagation" topic_path = topic_path_base + custom_str subscription_path = subscription_path_base + custom_str @@ -550,12 +556,12 @@ class CallbackError(Exception): def test_streaming_pull_ack_deadline( self, publisher, - subscriber, project, topic_path_base, subscription_path_base, cleanup, ): + subscriber = pubsub_v1.SubscriberClient(transport="grpc") custom_str = "-streaming-pull-ack-deadline" topic_path = topic_path_base + custom_str subscription_path = subscription_path_base + custom_str @@ -608,8 +614,9 @@ def test_streaming_pull_ack_deadline( subscription_future.cancel() def test_streaming_pull_max_messages( - self, publisher, topic_path_base, subscriber, subscription_path_base, cleanup + self, publisher, topic_path_base, subscription_path_base, cleanup ): + subscriber = pubsub_v1.SubscriberClient(transport="grpc") custom_str = "-streaming-pull-max-messages" topic_path = topic_path_base + custom_str subscription_path = subscription_path_base + custom_str @@ -668,8 +675,9 @@ def test_streaming_pull_max_messages( @typed_flaky def test_streaming_pull_blocking_shutdown( - self, publisher, topic_path_base, subscriber, subscription_path_base, cleanup + self, publisher, topic_path_base, subscription_path_base, cleanup ): + subscriber = pubsub_v1.SubscriberClient(transport="grpc") custom_str = "-streaming-pull-blocking-shutdown" topic_path = topic_path_base + custom_str subscription_path = subscription_path_base + custom_str @@ -753,8 +761,9 @@ def callback2(message): ) class TestBasicRBAC(object): def test_streaming_pull_subscriber_permissions_sufficient( - self, publisher, topic_path_base, subscriber, subscription_path_base, cleanup + self, publisher, topic_path_base, subscription_path_base, cleanup ): + subscriber = pubsub_v1.SubscriberClient(transport="grpc") custom_str = "-streaming-pull-subscriber-permissions-sufficient" topic_path = topic_path_base + custom_str subscription_path = subscription_path_base + custom_str diff --git a/tests/unit/gapic/pubsub_v1/test_publisher.py b/tests/unit/gapic/pubsub_v1/test_publisher.py index 6badf82d6..12d584e49 100644 --- a/tests/unit/gapic/pubsub_v1/test_publisher.py +++ b/tests/unit/gapic/pubsub_v1/test_publisher.py @@ -104,6 +104,7 @@ def test__get_default_mtls_endpoint(): [ (PublisherClient, "grpc"), (PublisherAsyncClient, "grpc_asyncio"), + (PublisherClient, "rest"), ], ) def test_publisher_client_from_service_account_info(client_class, transport_name): @@ -117,7 +118,11 @@ def test_publisher_client_from_service_account_info(client_class, transport_name assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("pubsub.googleapis.com:443") + assert client.transport._host == ( + "pubsub.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://pubsub.googleapis.com" + ) @pytest.mark.parametrize( @@ -125,6 +130,7 @@ def test_publisher_client_from_service_account_info(client_class, transport_name [ (transports.PublisherGrpcTransport, "grpc"), (transports.PublisherGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.PublisherRestTransport, "rest"), ], ) def test_publisher_client_service_account_always_use_jwt( @@ -150,6 +156,7 @@ def test_publisher_client_service_account_always_use_jwt( [ (PublisherClient, "grpc"), (PublisherAsyncClient, "grpc_asyncio"), + (PublisherClient, "rest"), ], ) def test_publisher_client_from_service_account_file(client_class, transport_name): @@ -170,13 +177,18 @@ def test_publisher_client_from_service_account_file(client_class, transport_name assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("pubsub.googleapis.com:443") + assert client.transport._host == ( + "pubsub.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://pubsub.googleapis.com" + ) def test_publisher_client_get_transport_class(): transport = PublisherClient.get_transport_class() available_transports = [ transports.PublisherGrpcTransport, + transports.PublisherRestTransport, ] assert transport in available_transports @@ -193,6 +205,7 @@ def test_publisher_client_get_transport_class(): transports.PublisherGrpcAsyncIOTransport, "grpc_asyncio", ), + (PublisherClient, transports.PublisherRestTransport, "rest"), ], ) @mock.patch.object( @@ -334,6 +347,8 @@ def test_publisher_client_client_options(client_class, transport_class, transpor "grpc_asyncio", "false", ), + (PublisherClient, transports.PublisherRestTransport, "rest", "true"), + (PublisherClient, transports.PublisherRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -527,6 +542,7 @@ def test_publisher_client_get_mtls_endpoint_and_cert_source(client_class): transports.PublisherGrpcAsyncIOTransport, "grpc_asyncio", ), + (PublisherClient, transports.PublisherRestTransport, "rest"), ], ) def test_publisher_client_client_options_scopes( @@ -562,6 +578,7 @@ def test_publisher_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (PublisherClient, transports.PublisherRestTransport, "rest", None), ], ) def test_publisher_client_client_options_credentials_file( @@ -3318,180 +3335,2705 @@ async def test_detach_subscription_field_headers_async(): ) in kw["metadata"] -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.PublisherGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + pubsub.Topic, + dict, + ], +) +def test_create_topic_rest(request_type): + client = PublisherClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = PublisherClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/topics/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 = pubsub.Topic( + name="name_value", + kms_key_name="kms_key_name_value", + satisfies_pzs=True, ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.PublisherGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = PublisherClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.Topic.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_topic(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pubsub.Topic) + assert response.name == "name_value" + assert response.kms_key_name == "kms_key_name_value" + assert response.satisfies_pzs is True + + +def test_create_topic_rest_required_fields(request_type=pubsub.Topic): + transport_class = transports.PublisherRestTransport + + 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, + including_default_value_fields=False, + use_integers_for_enums=False, ) + ) - # It is an error to provide an api_key and a transport instance. - transport = transports.PublisherGrpcTransport( + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_topic._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() + ).create_topic._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 = PublisherClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = pubsub.Topic() + # 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": "put", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = pubsub.Topic.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_topic(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_topic_rest_unset_required_fields(): + transport = transports.PublisherRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = PublisherClient( - client_options=options, - transport=transport, - ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = PublisherClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() - ) + unset_fields = transport.create_topic._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) - # It is an error to provide scopes and a transport instance. - transport = transports.PublisherGrpcTransport( + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_topic_rest_interceptors(null_interceptor): + transport = transports.PublisherRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.PublisherRestInterceptor(), ) - with pytest.raises(ValueError): - client = PublisherClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, + client = PublisherClient(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.PublisherRestInterceptor, "post_create_topic" + ) as post, mock.patch.object( + transports.PublisherRestInterceptor, "pre_create_topic" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = pubsub.Topic.pb(pubsub.Topic()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = pubsub.Topic.to_json(pubsub.Topic()) + + request = pubsub.Topic() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = pubsub.Topic() + + client.create_topic( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) + pre.assert_called_once() + post.assert_called_once() -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.PublisherGrpcTransport( + +def test_create_topic_rest_bad_request( + transport: str = "rest", request_type=pubsub.Topic +): + client = PublisherClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - client = PublisherClient(transport=transport) - assert client.transport is transport + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/topics/sample2"} + request = request_type(**request_init) -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.PublisherGrpcTransport( + # 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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_topic(request) + + +def test_create_topic_rest_flattened(): + client = PublisherClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - channel = transport.grpc_channel - assert channel - transport = transports.PublisherGrpcAsyncIOTransport( + # 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 = pubsub.Topic() + + # get arguments that satisfy an http rule for this method + sample_request = {"name": "projects/sample1/topics/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 + pb_return_value = pubsub.Topic.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_topic(**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=projects/*/topics/*}" % client.transport._host, args[1] + ) + + +def test_create_topic_rest_flattened_error(transport: str = "rest"): + client = PublisherClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_topic( + pubsub.Topic(), + name="name_value", + ) -@pytest.mark.parametrize( - "transport_class", - [ - transports.PublisherGrpcTransport, - transports.PublisherGrpcAsyncIOTransport, - ], -) -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_create_topic_rest_error(): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) @pytest.mark.parametrize( - "transport_name", + "request_type", [ - "grpc", + pubsub.UpdateTopicRequest, + dict, ], ) -def test_transport_kind(transport_name): - transport = PublisherClient.get_transport_class(transport_name)( +def test_update_topic_rest(request_type): + client = PublisherClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - assert transport.kind == transport_name + # send a request that will satisfy transcoding + request_init = {"topic": {"name": "projects/sample1/topics/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 = pubsub.Topic( + name="name_value", + kms_key_name="kms_key_name_value", + satisfies_pzs=True, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.Topic.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_topic(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pubsub.Topic) + assert response.name == "name_value" + assert response.kms_key_name == "kms_key_name_value" + assert response.satisfies_pzs is True + + +def test_update_topic_rest_required_fields(request_type=pubsub.UpdateTopicRequest): + transport_class = transports.PublisherRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_topic._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_topic._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. client = PublisherClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = pubsub.Topic() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = pubsub.Topic.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_topic(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_topic_rest_unset_required_fields(): + transport = transports.PublisherRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - assert isinstance( - client.transport, - transports.PublisherGrpcTransport, + + unset_fields = transport.update_topic._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "topic", + "updateMask", + ) + ) ) -def test_publisher_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.PublisherTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_topic_rest_interceptors(null_interceptor): + transport = transports.PublisherRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.PublisherRestInterceptor(), + ) + client = PublisherClient(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.PublisherRestInterceptor, "post_update_topic" + ) as post, mock.patch.object( + transports.PublisherRestInterceptor, "pre_update_topic" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = pubsub.UpdateTopicRequest.pb(pubsub.UpdateTopicRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = pubsub.Topic.to_json(pubsub.Topic()) + + request = pubsub.UpdateTopicRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = pubsub.Topic() + + client.update_topic( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) + pre.assert_called_once() + post.assert_called_once() -def test_publisher_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.pubsub_v1.services.publisher.transports.PublisherTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.PublisherTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "create_topic", - "update_topic", - "publish", - "get_topic", - "list_topics", - "list_topic_subscriptions", - "list_topic_snapshots", - "delete_topic", - "detach_subscription", - "set_iam_policy", - "get_iam_policy", - "test_iam_permissions", +def test_update_topic_rest_bad_request( + transport: str = "rest", request_type=pubsub.UpdateTopicRequest +): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) - with pytest.raises(NotImplementedError): - transport.close() + # send a request that will satisfy transcoding + request_init = {"topic": {"name": "projects/sample1/topics/sample2"}} + request = request_type(**request_init) - # Catch all for all remaining methods and properties - remainder = [ - "kind", - ] - for r in remainder: - with pytest.raises(NotImplementedError): - getattr(transport, r)() + # 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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_topic(request) -def test_publisher_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.pubsub_v1.services.publisher.transports.PublisherTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None +def test_update_topic_rest_flattened(): + client = PublisherClient( + 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 = pubsub.Topic() + + # get arguments that satisfy an http rule for this method + sample_request = {"topic": {"name": "projects/sample1/topics/sample2"}} + + # get truthy value for each flattened field + mock_args = dict( + topic=pubsub.Topic(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.Topic.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_topic(**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/{topic.name=projects/*/topics/*}" % client.transport._host, args[1] + ) + + +def test_update_topic_rest_flattened_error(transport: str = "rest"): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_topic( + pubsub.UpdateTopicRequest(), + topic=pubsub.Topic(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_topic_rest_error(): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.PublishRequest, + dict, + ], +) +def test_publish_rest(request_type): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"topic": "projects/sample1/topics/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 = pubsub.PublishResponse( + message_ids=["message_ids_value"], + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.PublishResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.publish(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pubsub.PublishResponse) + assert response.message_ids == ["message_ids_value"] + + +def test_publish_rest_required_fields(request_type=pubsub.PublishRequest): + transport_class = transports.PublisherRestTransport + + request_init = {} + request_init["topic"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).publish._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["topic"] = "topic_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).publish._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "topic" in jsonified_request + assert jsonified_request["topic"] == "topic_value" + + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = pubsub.PublishResponse() + # 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 + + pb_return_value = pubsub.PublishResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.publish(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_publish_rest_unset_required_fields(): + transport = transports.PublisherRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.publish._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "topic", + "messages", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_publish_rest_interceptors(null_interceptor): + transport = transports.PublisherRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.PublisherRestInterceptor(), + ) + client = PublisherClient(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.PublisherRestInterceptor, "post_publish" + ) as post, mock.patch.object( + transports.PublisherRestInterceptor, "pre_publish" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = pubsub.PublishRequest.pb(pubsub.PublishRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = pubsub.PublishResponse.to_json( + pubsub.PublishResponse() + ) + + request = pubsub.PublishRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = pubsub.PublishResponse() + + client.publish( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_publish_rest_bad_request( + transport: str = "rest", request_type=pubsub.PublishRequest +): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"topic": "projects/sample1/topics/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.publish(request) + + +def test_publish_rest_flattened(): + client = PublisherClient( + 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 = pubsub.PublishResponse() + + # get arguments that satisfy an http rule for this method + sample_request = {"topic": "projects/sample1/topics/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + topic="topic_value", + messages=[pubsub.PubsubMessage(data=b"data_blob")], + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.PublishResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.publish(**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/{topic=projects/*/topics/*}:publish" % client.transport._host, + args[1], + ) + + +def test_publish_rest_flattened_error(transport: str = "rest"): + client = PublisherClient( + 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.publish( + pubsub.PublishRequest(), + topic="topic_value", + messages=[pubsub.PubsubMessage(data=b"data_blob")], + ) + + +def test_publish_rest_error(): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.GetTopicRequest, + dict, + ], +) +def test_get_topic_rest(request_type): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"topic": "projects/sample1/topics/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 = pubsub.Topic( + name="name_value", + kms_key_name="kms_key_name_value", + satisfies_pzs=True, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.Topic.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_topic(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pubsub.Topic) + assert response.name == "name_value" + assert response.kms_key_name == "kms_key_name_value" + assert response.satisfies_pzs is True + + +def test_get_topic_rest_required_fields(request_type=pubsub.GetTopicRequest): + transport_class = transports.PublisherRestTransport + + request_init = {} + request_init["topic"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_topic._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["topic"] = "topic_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_topic._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "topic" in jsonified_request + assert jsonified_request["topic"] == "topic_value" + + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = pubsub.Topic() + # 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 + + pb_return_value = pubsub.Topic.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_topic(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_topic_rest_unset_required_fields(): + transport = transports.PublisherRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_topic._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("topic",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_topic_rest_interceptors(null_interceptor): + transport = transports.PublisherRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.PublisherRestInterceptor(), + ) + client = PublisherClient(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.PublisherRestInterceptor, "post_get_topic" + ) as post, mock.patch.object( + transports.PublisherRestInterceptor, "pre_get_topic" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = pubsub.GetTopicRequest.pb(pubsub.GetTopicRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = pubsub.Topic.to_json(pubsub.Topic()) + + request = pubsub.GetTopicRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = pubsub.Topic() + + client.get_topic( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_topic_rest_bad_request( + transport: str = "rest", request_type=pubsub.GetTopicRequest +): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"topic": "projects/sample1/topics/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_topic(request) + + +def test_get_topic_rest_flattened(): + client = PublisherClient( + 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 = pubsub.Topic() + + # get arguments that satisfy an http rule for this method + sample_request = {"topic": "projects/sample1/topics/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + topic="topic_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.Topic.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_topic(**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/{topic=projects/*/topics/*}" % client.transport._host, args[1] + ) + + +def test_get_topic_rest_flattened_error(transport: str = "rest"): + client = PublisherClient( + 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_topic( + pubsub.GetTopicRequest(), + topic="topic_value", + ) + + +def test_get_topic_rest_error(): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.ListTopicsRequest, + dict, + ], +) +def test_list_topics_rest(request_type): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"project": "projects/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 = pubsub.ListTopicsResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.ListTopicsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_topics(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListTopicsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_topics_rest_required_fields(request_type=pubsub.ListTopicsRequest): + transport_class = transports.PublisherRestTransport + + request_init = {} + request_init["project"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_topics._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["project"] = "project_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_topics._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 "project" in jsonified_request + assert jsonified_request["project"] == "project_value" + + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = pubsub.ListTopicsResponse() + # 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 + + pb_return_value = pubsub.ListTopicsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_topics(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_topics_rest_unset_required_fields(): + transport = transports.PublisherRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_topics._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("project",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_topics_rest_interceptors(null_interceptor): + transport = transports.PublisherRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.PublisherRestInterceptor(), + ) + client = PublisherClient(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.PublisherRestInterceptor, "post_list_topics" + ) as post, mock.patch.object( + transports.PublisherRestInterceptor, "pre_list_topics" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = pubsub.ListTopicsRequest.pb(pubsub.ListTopicsRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = pubsub.ListTopicsResponse.to_json( + pubsub.ListTopicsResponse() + ) + + request = pubsub.ListTopicsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = pubsub.ListTopicsResponse() + + client.list_topics( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_list_topics_rest_bad_request( + transport: str = "rest", request_type=pubsub.ListTopicsRequest +): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"project": "projects/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_topics(request) + + +def test_list_topics_rest_flattened(): + client = PublisherClient( + 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 = pubsub.ListTopicsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = {"project": "projects/sample1"} + + # get truthy value for each flattened field + mock_args = dict( + project="project_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.ListTopicsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_topics(**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/{project=projects/*}/topics" % client.transport._host, args[1] + ) + + +def test_list_topics_rest_flattened_error(transport: str = "rest"): + client = PublisherClient( + 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_topics( + pubsub.ListTopicsRequest(), + project="project_value", + ) + + +def test_list_topics_rest_pager(transport: str = "rest"): + client = PublisherClient( + 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 = ( + pubsub.ListTopicsResponse( + topics=[ + pubsub.Topic(), + pubsub.Topic(), + pubsub.Topic(), + ], + next_page_token="abc", + ), + pubsub.ListTopicsResponse( + topics=[], + next_page_token="def", + ), + pubsub.ListTopicsResponse( + topics=[ + pubsub.Topic(), + ], + next_page_token="ghi", + ), + pubsub.ListTopicsResponse( + topics=[ + pubsub.Topic(), + pubsub.Topic(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(pubsub.ListTopicsResponse.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 = {"project": "projects/sample1"} + + pager = client.list_topics(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, pubsub.Topic) for i in results) + + pages = list(client.list_topics(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.ListTopicSubscriptionsRequest, + dict, + ], +) +def test_list_topic_subscriptions_rest(request_type): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"topic": "projects/sample1/topics/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 = pubsub.ListTopicSubscriptionsResponse( + subscriptions=["subscriptions_value"], + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.ListTopicSubscriptionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_topic_subscriptions(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListTopicSubscriptionsPager) + assert response.subscriptions == ["subscriptions_value"] + assert response.next_page_token == "next_page_token_value" + + +def test_list_topic_subscriptions_rest_required_fields( + request_type=pubsub.ListTopicSubscriptionsRequest, +): + transport_class = transports.PublisherRestTransport + + request_init = {} + request_init["topic"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_topic_subscriptions._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["topic"] = "topic_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_topic_subscriptions._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 "topic" in jsonified_request + assert jsonified_request["topic"] == "topic_value" + + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = pubsub.ListTopicSubscriptionsResponse() + # 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 + + pb_return_value = pubsub.ListTopicSubscriptionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_topic_subscriptions(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_topic_subscriptions_rest_unset_required_fields(): + transport = transports.PublisherRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_topic_subscriptions._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("topic",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_topic_subscriptions_rest_interceptors(null_interceptor): + transport = transports.PublisherRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.PublisherRestInterceptor(), + ) + client = PublisherClient(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.PublisherRestInterceptor, "post_list_topic_subscriptions" + ) as post, mock.patch.object( + transports.PublisherRestInterceptor, "pre_list_topic_subscriptions" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = pubsub.ListTopicSubscriptionsRequest.pb( + pubsub.ListTopicSubscriptionsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = pubsub.ListTopicSubscriptionsResponse.to_json( + pubsub.ListTopicSubscriptionsResponse() + ) + + request = pubsub.ListTopicSubscriptionsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = pubsub.ListTopicSubscriptionsResponse() + + client.list_topic_subscriptions( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_list_topic_subscriptions_rest_bad_request( + transport: str = "rest", request_type=pubsub.ListTopicSubscriptionsRequest +): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"topic": "projects/sample1/topics/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_topic_subscriptions(request) + + +def test_list_topic_subscriptions_rest_flattened(): + client = PublisherClient( + 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 = pubsub.ListTopicSubscriptionsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = {"topic": "projects/sample1/topics/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + topic="topic_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.ListTopicSubscriptionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_topic_subscriptions(**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/{topic=projects/*/topics/*}/subscriptions" % client.transport._host, + args[1], + ) + + +def test_list_topic_subscriptions_rest_flattened_error(transport: str = "rest"): + client = PublisherClient( + 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_topic_subscriptions( + pubsub.ListTopicSubscriptionsRequest(), + topic="topic_value", + ) + + +def test_list_topic_subscriptions_rest_pager(transport: str = "rest"): + client = PublisherClient( + 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 = ( + pubsub.ListTopicSubscriptionsResponse( + subscriptions=[ + str(), + str(), + str(), + ], + next_page_token="abc", + ), + pubsub.ListTopicSubscriptionsResponse( + subscriptions=[], + next_page_token="def", + ), + pubsub.ListTopicSubscriptionsResponse( + subscriptions=[ + str(), + ], + next_page_token="ghi", + ), + pubsub.ListTopicSubscriptionsResponse( + subscriptions=[ + str(), + str(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + pubsub.ListTopicSubscriptionsResponse.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 = {"topic": "projects/sample1/topics/sample2"} + + pager = client.list_topic_subscriptions(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, str) for i in results) + + pages = list(client.list_topic_subscriptions(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.ListTopicSnapshotsRequest, + dict, + ], +) +def test_list_topic_snapshots_rest(request_type): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"topic": "projects/sample1/topics/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 = pubsub.ListTopicSnapshotsResponse( + snapshots=["snapshots_value"], + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.ListTopicSnapshotsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_topic_snapshots(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListTopicSnapshotsPager) + assert response.snapshots == ["snapshots_value"] + assert response.next_page_token == "next_page_token_value" + + +def test_list_topic_snapshots_rest_required_fields( + request_type=pubsub.ListTopicSnapshotsRequest, +): + transport_class = transports.PublisherRestTransport + + request_init = {} + request_init["topic"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_topic_snapshots._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["topic"] = "topic_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_topic_snapshots._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 "topic" in jsonified_request + assert jsonified_request["topic"] == "topic_value" + + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = pubsub.ListTopicSnapshotsResponse() + # 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 + + pb_return_value = pubsub.ListTopicSnapshotsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_topic_snapshots(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_topic_snapshots_rest_unset_required_fields(): + transport = transports.PublisherRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_topic_snapshots._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("topic",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_topic_snapshots_rest_interceptors(null_interceptor): + transport = transports.PublisherRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.PublisherRestInterceptor(), + ) + client = PublisherClient(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.PublisherRestInterceptor, "post_list_topic_snapshots" + ) as post, mock.patch.object( + transports.PublisherRestInterceptor, "pre_list_topic_snapshots" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = pubsub.ListTopicSnapshotsRequest.pb( + pubsub.ListTopicSnapshotsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = pubsub.ListTopicSnapshotsResponse.to_json( + pubsub.ListTopicSnapshotsResponse() + ) + + request = pubsub.ListTopicSnapshotsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = pubsub.ListTopicSnapshotsResponse() + + client.list_topic_snapshots( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_list_topic_snapshots_rest_bad_request( + transport: str = "rest", request_type=pubsub.ListTopicSnapshotsRequest +): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"topic": "projects/sample1/topics/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_topic_snapshots(request) + + +def test_list_topic_snapshots_rest_flattened(): + client = PublisherClient( + 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 = pubsub.ListTopicSnapshotsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = {"topic": "projects/sample1/topics/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + topic="topic_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.ListTopicSnapshotsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_topic_snapshots(**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/{topic=projects/*/topics/*}/snapshots" % client.transport._host, + args[1], + ) + + +def test_list_topic_snapshots_rest_flattened_error(transport: str = "rest"): + client = PublisherClient( + 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_topic_snapshots( + pubsub.ListTopicSnapshotsRequest(), + topic="topic_value", + ) + + +def test_list_topic_snapshots_rest_pager(transport: str = "rest"): + client = PublisherClient( + 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 = ( + pubsub.ListTopicSnapshotsResponse( + snapshots=[ + str(), + str(), + str(), + ], + next_page_token="abc", + ), + pubsub.ListTopicSnapshotsResponse( + snapshots=[], + next_page_token="def", + ), + pubsub.ListTopicSnapshotsResponse( + snapshots=[ + str(), + ], + next_page_token="ghi", + ), + pubsub.ListTopicSnapshotsResponse( + snapshots=[ + str(), + str(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(pubsub.ListTopicSnapshotsResponse.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 = {"topic": "projects/sample1/topics/sample2"} + + pager = client.list_topic_snapshots(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, str) for i in results) + + pages = list(client.list_topic_snapshots(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.DeleteTopicRequest, + dict, + ], +) +def test_delete_topic_rest(request_type): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"topic": "projects/sample1/topics/sample2"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_topic(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_topic_rest_required_fields(request_type=pubsub.DeleteTopicRequest): + transport_class = transports.PublisherRestTransport + + request_init = {} + request_init["topic"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_topic._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["topic"] = "topic_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_topic._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "topic" in jsonified_request + assert jsonified_request["topic"] == "topic_value" + + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_topic(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_topic_rest_unset_required_fields(): + transport = transports.PublisherRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_topic._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("topic",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_topic_rest_interceptors(null_interceptor): + transport = transports.PublisherRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.PublisherRestInterceptor(), + ) + client = PublisherClient(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.PublisherRestInterceptor, "pre_delete_topic" + ) as pre: + pre.assert_not_called() + pb_message = pubsub.DeleteTopicRequest.pb(pubsub.DeleteTopicRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = pubsub.DeleteTopicRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_topic( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_topic_rest_bad_request( + transport: str = "rest", request_type=pubsub.DeleteTopicRequest +): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"topic": "projects/sample1/topics/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_topic(request) + + +def test_delete_topic_rest_flattened(): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = {"topic": "projects/sample1/topics/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + topic="topic_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_topic(**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/{topic=projects/*/topics/*}" % client.transport._host, args[1] + ) + + +def test_delete_topic_rest_flattened_error(transport: str = "rest"): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_topic( + pubsub.DeleteTopicRequest(), + topic="topic_value", + ) + + +def test_delete_topic_rest_error(): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.DetachSubscriptionRequest, + dict, + ], +) +def test_detach_subscription_rest(request_type): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"subscription": "projects/sample1/subscriptions/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 = pubsub.DetachSubscriptionResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.DetachSubscriptionResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.detach_subscription(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pubsub.DetachSubscriptionResponse) + + +def test_detach_subscription_rest_required_fields( + request_type=pubsub.DetachSubscriptionRequest, +): + transport_class = transports.PublisherRestTransport + + request_init = {} + request_init["subscription"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).detach_subscription._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["subscription"] = "subscription_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).detach_subscription._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "subscription" in jsonified_request + assert jsonified_request["subscription"] == "subscription_value" + + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = pubsub.DetachSubscriptionResponse() + # 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.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = pubsub.DetachSubscriptionResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.detach_subscription(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_detach_subscription_rest_unset_required_fields(): + transport = transports.PublisherRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.detach_subscription._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("subscription",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_detach_subscription_rest_interceptors(null_interceptor): + transport = transports.PublisherRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None if null_interceptor else transports.PublisherRestInterceptor(), + ) + client = PublisherClient(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.PublisherRestInterceptor, "post_detach_subscription" + ) as post, mock.patch.object( + transports.PublisherRestInterceptor, "pre_detach_subscription" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = pubsub.DetachSubscriptionRequest.pb( + pubsub.DetachSubscriptionRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = pubsub.DetachSubscriptionResponse.to_json( + pubsub.DetachSubscriptionResponse() + ) + + request = pubsub.DetachSubscriptionRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = pubsub.DetachSubscriptionResponse() + + client.detach_subscription( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_detach_subscription_rest_bad_request( + transport: str = "rest", request_type=pubsub.DetachSubscriptionRequest +): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"subscription": "projects/sample1/subscriptions/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.detach_subscription(request) + + +def test_detach_subscription_rest_error(): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.PublisherGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.PublisherGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = PublisherClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.PublisherGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = PublisherClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = PublisherClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.PublisherGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = PublisherClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.PublisherGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = PublisherClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.PublisherGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.PublisherGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.PublisherGrpcTransport, + transports.PublisherGrpcAsyncIOTransport, + transports.PublisherRestTransport, + ], +) +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() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = PublisherClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.PublisherGrpcTransport, + ) + + +def test_publisher_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.PublisherTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_publisher_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.pubsub_v1.services.publisher.transports.PublisherTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.PublisherTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "create_topic", + "update_topic", + "publish", + "get_topic", + "list_topics", + "list_topic_subscriptions", + "list_topic_snapshots", + "delete_topic", + "detach_subscription", + "set_iam_policy", + "get_iam_policy", + "test_iam_permissions", + ) + 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_publisher_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.pubsub_v1.services.publisher.transports.PublisherTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) transport = transports.PublisherTransport( credentials_file="credentials.json", @@ -3562,6 +6104,7 @@ def test_publisher_transport_auth_adc(transport_class): [ transports.PublisherGrpcTransport, transports.PublisherGrpcAsyncIOTransport, + transports.PublisherRestTransport, ], ) def test_publisher_transport_auth_gdch_credentials(transport_class): @@ -3663,11 +6206,23 @@ def test_publisher_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_publisher_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.PublisherRestTransport( + 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_publisher_host_no_port(transport_name): @@ -3678,7 +6233,11 @@ def test_publisher_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("pubsub.googleapis.com:443") + assert client.transport._host == ( + "pubsub.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://pubsub.googleapis.com" + ) @pytest.mark.parametrize( @@ -3686,6 +6245,7 @@ def test_publisher_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_publisher_host_with_port(transport_name): @@ -3696,7 +6256,57 @@ def test_publisher_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("pubsub.googleapis.com:8000") + assert client.transport._host == ( + "pubsub.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://pubsub.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_publisher_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = PublisherClient( + credentials=creds1, + transport=transport_name, + ) + client2 = PublisherClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.create_topic._session + session2 = client2.transport.create_topic._session + assert session1 != session2 + session1 = client1.transport.update_topic._session + session2 = client2.transport.update_topic._session + assert session1 != session2 + session1 = client1.transport.publish._session + session2 = client2.transport.publish._session + assert session1 != session2 + session1 = client1.transport.get_topic._session + session2 = client2.transport.get_topic._session + assert session1 != session2 + session1 = client1.transport.list_topics._session + session2 = client2.transport.list_topics._session + assert session1 != session2 + session1 = client1.transport.list_topic_subscriptions._session + session2 = client2.transport.list_topic_subscriptions._session + assert session1 != session2 + session1 = client1.transport.list_topic_snapshots._session + session2 = client2.transport.list_topic_snapshots._session + assert session1 != session2 + session1 = client1.transport.delete_topic._session + session2 = client2.transport.delete_topic._session + assert session1 != session2 + session1 = client1.transport.detach_subscription._session + session2 = client2.transport.detach_subscription._session + assert session1 != session2 def test_publisher_grpc_transport_channel(): @@ -4030,6 +6640,180 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_iam_policy_rest_bad_request( + transport: str = "rest", request_type=iam_policy_pb2.GetIamPolicyRequest +): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"resource": "projects/sample1/topics/sample2"}, request + ) + + # 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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_iam_policy(request) + + +@pytest.mark.parametrize( + "request_type", + [ + iam_policy_pb2.GetIamPolicyRequest, + dict, + ], +) +def test_get_iam_policy_rest(request_type): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"resource": "projects/sample1/topics/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 = policy_pb2.Policy() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_iam_policy(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, policy_pb2.Policy) + + +def test_set_iam_policy_rest_bad_request( + transport: str = "rest", request_type=iam_policy_pb2.SetIamPolicyRequest +): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"resource": "projects/sample1/topics/sample2"}, request + ) + + # 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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.set_iam_policy(request) + + +@pytest.mark.parametrize( + "request_type", + [ + iam_policy_pb2.SetIamPolicyRequest, + dict, + ], +) +def test_set_iam_policy_rest(request_type): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"resource": "projects/sample1/topics/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 = policy_pb2.Policy() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.set_iam_policy(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, policy_pb2.Policy) + + +def test_test_iam_permissions_rest_bad_request( + transport: str = "rest", request_type=iam_policy_pb2.TestIamPermissionsRequest +): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"resource": "projects/sample1/subscriptions/sample2"}, request + ) + + # 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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.test_iam_permissions(request) + + +@pytest.mark.parametrize( + "request_type", + [ + iam_policy_pb2.TestIamPermissionsRequest, + dict, + ], +) +def test_test_iam_permissions_rest(request_type): + client = PublisherClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"resource": "projects/sample1/subscriptions/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 = iam_policy_pb2.TestIamPermissionsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.test_iam_permissions(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, iam_policy_pb2.TestIamPermissionsResponse) + + def test_set_iam_policy(transport: str = "grpc"): client = PublisherClient( credentials=ga_credentials.AnonymousCredentials(), @@ -4540,6 +7324,7 @@ async def test_test_iam_permissions_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -4557,6 +7342,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/pubsub_v1/test_schema_service.py b/tests/unit/gapic/pubsub_v1/test_schema_service.py index 54b8d8ac3..132136a38 100644 --- a/tests/unit/gapic/pubsub_v1/test_schema_service.py +++ b/tests/unit/gapic/pubsub_v1/test_schema_service.py @@ -107,6 +107,7 @@ def test__get_default_mtls_endpoint(): [ (SchemaServiceClient, "grpc"), (SchemaServiceAsyncClient, "grpc_asyncio"), + (SchemaServiceClient, "rest"), ], ) def test_schema_service_client_from_service_account_info(client_class, transport_name): @@ -120,7 +121,11 @@ def test_schema_service_client_from_service_account_info(client_class, transport assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("pubsub.googleapis.com:443") + assert client.transport._host == ( + "pubsub.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://pubsub.googleapis.com" + ) @pytest.mark.parametrize( @@ -128,6 +133,7 @@ def test_schema_service_client_from_service_account_info(client_class, transport [ (transports.SchemaServiceGrpcTransport, "grpc"), (transports.SchemaServiceGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.SchemaServiceRestTransport, "rest"), ], ) def test_schema_service_client_service_account_always_use_jwt( @@ -153,6 +159,7 @@ def test_schema_service_client_service_account_always_use_jwt( [ (SchemaServiceClient, "grpc"), (SchemaServiceAsyncClient, "grpc_asyncio"), + (SchemaServiceClient, "rest"), ], ) def test_schema_service_client_from_service_account_file(client_class, transport_name): @@ -173,13 +180,18 @@ def test_schema_service_client_from_service_account_file(client_class, transport assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("pubsub.googleapis.com:443") + assert client.transport._host == ( + "pubsub.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://pubsub.googleapis.com" + ) def test_schema_service_client_get_transport_class(): transport = SchemaServiceClient.get_transport_class() available_transports = [ transports.SchemaServiceGrpcTransport, + transports.SchemaServiceRestTransport, ] assert transport in available_transports @@ -196,6 +208,7 @@ def test_schema_service_client_get_transport_class(): transports.SchemaServiceGrpcAsyncIOTransport, "grpc_asyncio", ), + (SchemaServiceClient, transports.SchemaServiceRestTransport, "rest"), ], ) @mock.patch.object( @@ -341,6 +354,8 @@ def test_schema_service_client_client_options( "grpc_asyncio", "false", ), + (SchemaServiceClient, transports.SchemaServiceRestTransport, "rest", "true"), + (SchemaServiceClient, transports.SchemaServiceRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -540,6 +555,7 @@ def test_schema_service_client_get_mtls_endpoint_and_cert_source(client_class): transports.SchemaServiceGrpcAsyncIOTransport, "grpc_asyncio", ), + (SchemaServiceClient, transports.SchemaServiceRestTransport, "rest"), ], ) def test_schema_service_client_client_options_scopes( @@ -580,6 +596,7 @@ def test_schema_service_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (SchemaServiceClient, transports.SchemaServiceRestTransport, "rest", None), ], ) def test_schema_service_client_client_options_credentials_file( @@ -3423,202 +3440,3008 @@ async def test_validate_message_field_headers_async(): ) in kw["metadata"] -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.SchemaServiceGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + gp_schema.CreateSchemaRequest, + dict, + ], +) +def test_create_schema_rest(request_type): + client = SchemaServiceClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = SchemaServiceClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1"} + request_init["schema"] = { + "name": "name_value", + "type_": 1, + "definition": "definition_value", + "revision_id": "revision_id_value", + "revision_create_time": {"seconds": 751, "nanos": 543}, + } + 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 = gp_schema.Schema( + name="name_value", + type_=gp_schema.Schema.Type.PROTOCOL_BUFFER, + definition="definition_value", + revision_id="revision_id_value", ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.SchemaServiceGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = SchemaServiceClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gp_schema.Schema.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_schema(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gp_schema.Schema) + assert response.name == "name_value" + assert response.type_ == gp_schema.Schema.Type.PROTOCOL_BUFFER + assert response.definition == "definition_value" + assert response.revision_id == "revision_id_value" + + +def test_create_schema_rest_required_fields(request_type=gp_schema.CreateSchemaRequest): + transport_class = transports.SchemaServiceRestTransport + + 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, + including_default_value_fields=False, + use_integers_for_enums=False, ) + ) - # It is an error to provide an api_key and a transport instance. - transport = transports.SchemaServiceGrpcTransport( + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_schema._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() + ).create_schema._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("schema_id",)) + 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 = SchemaServiceClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gp_schema.Schema() + # 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 + + pb_return_value = gp_schema.Schema.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_schema(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_schema_rest_unset_required_fields(): + transport = transports.SchemaServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = SchemaServiceClient( - client_options=options, - transport=transport, - ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = SchemaServiceClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + unset_fields = transport.create_schema._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(("schemaId",)) + & set( + ( + "parent", + "schema", + ) ) + ) - # It is an error to provide scopes and a transport instance. - transport = transports.SchemaServiceGrpcTransport( + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_schema_rest_interceptors(null_interceptor): + transport = transports.SchemaServiceRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SchemaServiceRestInterceptor(), ) - with pytest.raises(ValueError): - client = SchemaServiceClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, + client = SchemaServiceClient(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.SchemaServiceRestInterceptor, "post_create_schema" + ) as post, mock.patch.object( + transports.SchemaServiceRestInterceptor, "pre_create_schema" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gp_schema.CreateSchemaRequest.pb(gp_schema.CreateSchemaRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gp_schema.Schema.to_json(gp_schema.Schema()) + + request = gp_schema.CreateSchemaRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gp_schema.Schema() + + client.create_schema( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) + pre.assert_called_once() + post.assert_called_once() -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.SchemaServiceGrpcTransport( + +def test_create_schema_rest_bad_request( + transport: str = "rest", request_type=gp_schema.CreateSchemaRequest +): + client = SchemaServiceClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - client = SchemaServiceClient(transport=transport) - assert client.transport is transport + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1"} + request_init["schema"] = { + "name": "name_value", + "type_": 1, + "definition": "definition_value", + "revision_id": "revision_id_value", + "revision_create_time": {"seconds": 751, "nanos": 543}, + } + request = request_type(**request_init) -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.SchemaServiceGrpcTransport( + # 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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_schema(request) + + +def test_create_schema_rest_flattened(): + client = SchemaServiceClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - channel = transport.grpc_channel - assert channel - transport = transports.SchemaServiceGrpcAsyncIOTransport( + # 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 = gp_schema.Schema() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + schema=gp_schema.Schema(name="name_value"), + schema_id="schema_id_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gp_schema.Schema.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_schema(**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=projects/*}/schemas" % client.transport._host, args[1] + ) + + +def test_create_schema_rest_flattened_error(transport: str = "rest"): + client = SchemaServiceClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_schema( + gp_schema.CreateSchemaRequest(), + parent="parent_value", + schema=gp_schema.Schema(name="name_value"), + schema_id="schema_id_value", + ) -@pytest.mark.parametrize( - "transport_class", - [ - transports.SchemaServiceGrpcTransport, - transports.SchemaServiceGrpcAsyncIOTransport, - ], -) -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_create_schema_rest_error(): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) @pytest.mark.parametrize( - "transport_name", + "request_type", [ - "grpc", + schema.GetSchemaRequest, + dict, ], ) -def test_transport_kind(transport_name): - transport = SchemaServiceClient.get_transport_class(transport_name)( - credentials=ga_credentials.AnonymousCredentials(), - ) - assert transport.kind == transport_name - - -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. +def test_get_schema_rest(request_type): client = SchemaServiceClient( credentials=ga_credentials.AnonymousCredentials(), - ) - assert isinstance( - client.transport, - transports.SchemaServiceGrpcTransport, + transport="rest", ) + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/schemas/sample2"} + request = request_type(**request_init) -def test_schema_service_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.SchemaServiceTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", + # 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 = schema.Schema( + name="name_value", + type_=schema.Schema.Type.PROTOCOL_BUFFER, + definition="definition_value", + revision_id="revision_id_value", ) + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = schema.Schema.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) -def test_schema_service_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.pubsub_v1.services.schema_service.transports.SchemaServiceTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.SchemaServiceTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_schema(request) - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "create_schema", - "get_schema", - "list_schemas", - "list_schema_revisions", - "commit_schema", - "rollback_schema", - "delete_schema_revision", - "delete_schema", - "validate_schema", - "validate_message", - "set_iam_policy", - "get_iam_policy", - "test_iam_permissions", + # Establish that the response is the type that we expect. + assert isinstance(response, schema.Schema) + assert response.name == "name_value" + assert response.type_ == schema.Schema.Type.PROTOCOL_BUFFER + assert response.definition == "definition_value" + assert response.revision_id == "revision_id_value" + + +def test_get_schema_rest_required_fields(request_type=schema.GetSchemaRequest): + transport_class = transports.SchemaServiceRestTransport + + 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, + including_default_value_fields=False, + use_integers_for_enums=False, + ) ) - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) - with pytest.raises(NotImplementedError): - transport.close() + # verify fields with default values are dropped - # Catch all for all remaining methods and properties - remainder = [ - "kind", - ] - for r in remainder: - with pytest.raises(NotImplementedError): - getattr(transport, r)() + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_schema._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + # verify required fields with default values are now present -def test_schema_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.pubsub_v1.services.schema_service.transports.SchemaServiceTransport._prep_wrapped_messages" - ) as Transport: - Transport.return_value = None - load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) - transport = transports.SchemaServiceTransport( - 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/pubsub", - ), - quota_project_id="octopus", - ) + jsonified_request["name"] = "name_value" + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_schema._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("view",)) + jsonified_request.update(unset_fields) -def test_schema_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.pubsub_v1.services.schema_service.transports.SchemaServiceTransport._prep_wrapped_messages" - ) as Transport: + # verify required fields with non-default values are left alone + assert "name" in jsonified_request + assert jsonified_request["name"] == "name_value" + + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = schema.Schema() + # 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 + + pb_return_value = schema.Schema.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_schema(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_schema_rest_unset_required_fields(): + transport = transports.SchemaServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_schema._get_unset_required_fields({}) + assert set(unset_fields) == (set(("view",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_schema_rest_interceptors(null_interceptor): + transport = transports.SchemaServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SchemaServiceRestInterceptor(), + ) + client = SchemaServiceClient(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.SchemaServiceRestInterceptor, "post_get_schema" + ) as post, mock.patch.object( + transports.SchemaServiceRestInterceptor, "pre_get_schema" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = schema.GetSchemaRequest.pb(schema.GetSchemaRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = schema.Schema.to_json(schema.Schema()) + + request = schema.GetSchemaRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = schema.Schema() + + client.get_schema( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_schema_rest_bad_request( + transport: str = "rest", request_type=schema.GetSchemaRequest +): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/schemas/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_schema(request) + + +def test_get_schema_rest_flattened(): + client = SchemaServiceClient( + 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 = schema.Schema() + + # get arguments that satisfy an http rule for this method + sample_request = {"name": "projects/sample1/schemas/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 + pb_return_value = schema.Schema.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_schema(**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=projects/*/schemas/*}" % client.transport._host, args[1] + ) + + +def test_get_schema_rest_flattened_error(transport: str = "rest"): + client = SchemaServiceClient( + 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_schema( + schema.GetSchemaRequest(), + name="name_value", + ) + + +def test_get_schema_rest_error(): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + schema.ListSchemasRequest, + dict, + ], +) +def test_list_schemas_rest(request_type): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/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 = schema.ListSchemasResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = schema.ListSchemasResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_schemas(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListSchemasPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_schemas_rest_required_fields(request_type=schema.ListSchemasRequest): + transport_class = transports.SchemaServiceRestTransport + + 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, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_schemas._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_schemas._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", + "view", + ) + ) + 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 = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = schema.ListSchemasResponse() + # 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 + + pb_return_value = schema.ListSchemasResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_schemas(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_schemas_rest_unset_required_fields(): + transport = transports.SchemaServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_schemas._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + "view", + ) + ) + & set(("parent",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_schemas_rest_interceptors(null_interceptor): + transport = transports.SchemaServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SchemaServiceRestInterceptor(), + ) + client = SchemaServiceClient(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.SchemaServiceRestInterceptor, "post_list_schemas" + ) as post, mock.patch.object( + transports.SchemaServiceRestInterceptor, "pre_list_schemas" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = schema.ListSchemasRequest.pb(schema.ListSchemasRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = schema.ListSchemasResponse.to_json( + schema.ListSchemasResponse() + ) + + request = schema.ListSchemasRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = schema.ListSchemasResponse() + + client.list_schemas( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_list_schemas_rest_bad_request( + transport: str = "rest", request_type=schema.ListSchemasRequest +): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_schemas(request) + + +def test_list_schemas_rest_flattened(): + client = SchemaServiceClient( + 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 = schema.ListSchemasResponse() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/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 + pb_return_value = schema.ListSchemasResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_schemas(**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=projects/*}/schemas" % client.transport._host, args[1] + ) + + +def test_list_schemas_rest_flattened_error(transport: str = "rest"): + client = SchemaServiceClient( + 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_schemas( + schema.ListSchemasRequest(), + parent="parent_value", + ) + + +def test_list_schemas_rest_pager(transport: str = "rest"): + client = SchemaServiceClient( + 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 = ( + schema.ListSchemasResponse( + schemas=[ + schema.Schema(), + schema.Schema(), + schema.Schema(), + ], + next_page_token="abc", + ), + schema.ListSchemasResponse( + schemas=[], + next_page_token="def", + ), + schema.ListSchemasResponse( + schemas=[ + schema.Schema(), + ], + next_page_token="ghi", + ), + schema.ListSchemasResponse( + schemas=[ + schema.Schema(), + schema.Schema(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(schema.ListSchemasResponse.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": "projects/sample1"} + + pager = client.list_schemas(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, schema.Schema) for i in results) + + pages = list(client.list_schemas(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + schema.ListSchemaRevisionsRequest, + dict, + ], +) +def test_list_schema_revisions_rest(request_type): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/schemas/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 = schema.ListSchemaRevisionsResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = schema.ListSchemaRevisionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_schema_revisions(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListSchemaRevisionsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_schema_revisions_rest_required_fields( + request_type=schema.ListSchemaRevisionsRequest, +): + transport_class = transports.SchemaServiceRestTransport + + 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, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_schema_revisions._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() + ).list_schema_revisions._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", + "view", + ) + ) + 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 = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = schema.ListSchemaRevisionsResponse() + # 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 + + pb_return_value = schema.ListSchemaRevisionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_schema_revisions(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_schema_revisions_rest_unset_required_fields(): + transport = transports.SchemaServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_schema_revisions._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + "view", + ) + ) + & set(("name",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_schema_revisions_rest_interceptors(null_interceptor): + transport = transports.SchemaServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SchemaServiceRestInterceptor(), + ) + client = SchemaServiceClient(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.SchemaServiceRestInterceptor, "post_list_schema_revisions" + ) as post, mock.patch.object( + transports.SchemaServiceRestInterceptor, "pre_list_schema_revisions" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = schema.ListSchemaRevisionsRequest.pb( + schema.ListSchemaRevisionsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = schema.ListSchemaRevisionsResponse.to_json( + schema.ListSchemaRevisionsResponse() + ) + + request = schema.ListSchemaRevisionsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = schema.ListSchemaRevisionsResponse() + + client.list_schema_revisions( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_list_schema_revisions_rest_bad_request( + transport: str = "rest", request_type=schema.ListSchemaRevisionsRequest +): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/schemas/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_schema_revisions(request) + + +def test_list_schema_revisions_rest_flattened(): + client = SchemaServiceClient( + 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 = schema.ListSchemaRevisionsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = {"name": "projects/sample1/schemas/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 + pb_return_value = schema.ListSchemaRevisionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_schema_revisions(**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=projects/*/schemas/*}:listRevisions" % client.transport._host, + args[1], + ) + + +def test_list_schema_revisions_rest_flattened_error(transport: str = "rest"): + client = SchemaServiceClient( + 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_schema_revisions( + schema.ListSchemaRevisionsRequest(), + name="name_value", + ) + + +def test_list_schema_revisions_rest_pager(transport: str = "rest"): + client = SchemaServiceClient( + 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 = ( + schema.ListSchemaRevisionsResponse( + schemas=[ + schema.Schema(), + schema.Schema(), + schema.Schema(), + ], + next_page_token="abc", + ), + schema.ListSchemaRevisionsResponse( + schemas=[], + next_page_token="def", + ), + schema.ListSchemaRevisionsResponse( + schemas=[ + schema.Schema(), + ], + next_page_token="ghi", + ), + schema.ListSchemaRevisionsResponse( + schemas=[ + schema.Schema(), + schema.Schema(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple( + schema.ListSchemaRevisionsResponse.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 = {"name": "projects/sample1/schemas/sample2"} + + pager = client.list_schema_revisions(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, schema.Schema) for i in results) + + pages = list(client.list_schema_revisions(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + gp_schema.CommitSchemaRequest, + dict, + ], +) +def test_commit_schema_rest(request_type): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/schemas/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 = gp_schema.Schema( + name="name_value", + type_=gp_schema.Schema.Type.PROTOCOL_BUFFER, + definition="definition_value", + revision_id="revision_id_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gp_schema.Schema.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.commit_schema(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gp_schema.Schema) + assert response.name == "name_value" + assert response.type_ == gp_schema.Schema.Type.PROTOCOL_BUFFER + assert response.definition == "definition_value" + assert response.revision_id == "revision_id_value" + + +def test_commit_schema_rest_required_fields(request_type=gp_schema.CommitSchemaRequest): + transport_class = transports.SchemaServiceRestTransport + + 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, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).commit_schema._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() + ).commit_schema._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 = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gp_schema.Schema() + # 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 + + pb_return_value = gp_schema.Schema.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.commit_schema(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_commit_schema_rest_unset_required_fields(): + transport = transports.SchemaServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.commit_schema._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "name", + "schema", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_commit_schema_rest_interceptors(null_interceptor): + transport = transports.SchemaServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SchemaServiceRestInterceptor(), + ) + client = SchemaServiceClient(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.SchemaServiceRestInterceptor, "post_commit_schema" + ) as post, mock.patch.object( + transports.SchemaServiceRestInterceptor, "pre_commit_schema" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gp_schema.CommitSchemaRequest.pb(gp_schema.CommitSchemaRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gp_schema.Schema.to_json(gp_schema.Schema()) + + request = gp_schema.CommitSchemaRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gp_schema.Schema() + + client.commit_schema( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_commit_schema_rest_bad_request( + transport: str = "rest", request_type=gp_schema.CommitSchemaRequest +): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/schemas/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.commit_schema(request) + + +def test_commit_schema_rest_flattened(): + client = SchemaServiceClient( + 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 = gp_schema.Schema() + + # get arguments that satisfy an http rule for this method + sample_request = {"name": "projects/sample1/schemas/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + schema=gp_schema.Schema(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gp_schema.Schema.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.commit_schema(**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=projects/*/schemas/*}:commit" % client.transport._host, args[1] + ) + + +def test_commit_schema_rest_flattened_error(transport: str = "rest"): + client = SchemaServiceClient( + 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.commit_schema( + gp_schema.CommitSchemaRequest(), + name="name_value", + schema=gp_schema.Schema(name="name_value"), + ) + + +def test_commit_schema_rest_error(): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + schema.RollbackSchemaRequest, + dict, + ], +) +def test_rollback_schema_rest(request_type): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/schemas/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 = schema.Schema( + name="name_value", + type_=schema.Schema.Type.PROTOCOL_BUFFER, + definition="definition_value", + revision_id="revision_id_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = schema.Schema.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.rollback_schema(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, schema.Schema) + assert response.name == "name_value" + assert response.type_ == schema.Schema.Type.PROTOCOL_BUFFER + assert response.definition == "definition_value" + assert response.revision_id == "revision_id_value" + + +def test_rollback_schema_rest_required_fields( + request_type=schema.RollbackSchemaRequest, +): + transport_class = transports.SchemaServiceRestTransport + + request_init = {} + request_init["name"] = "" + request_init["revision_id"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).rollback_schema._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" + jsonified_request["revisionId"] = "revision_id_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).rollback_schema._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" + assert "revisionId" in jsonified_request + assert jsonified_request["revisionId"] == "revision_id_value" + + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = schema.Schema() + # 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 + + pb_return_value = schema.Schema.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.rollback_schema(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_rollback_schema_rest_unset_required_fields(): + transport = transports.SchemaServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.rollback_schema._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "name", + "revisionId", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_rollback_schema_rest_interceptors(null_interceptor): + transport = transports.SchemaServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SchemaServiceRestInterceptor(), + ) + client = SchemaServiceClient(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.SchemaServiceRestInterceptor, "post_rollback_schema" + ) as post, mock.patch.object( + transports.SchemaServiceRestInterceptor, "pre_rollback_schema" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = schema.RollbackSchemaRequest.pb(schema.RollbackSchemaRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = schema.Schema.to_json(schema.Schema()) + + request = schema.RollbackSchemaRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = schema.Schema() + + client.rollback_schema( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_rollback_schema_rest_bad_request( + transport: str = "rest", request_type=schema.RollbackSchemaRequest +): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/schemas/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.rollback_schema(request) + + +def test_rollback_schema_rest_flattened(): + client = SchemaServiceClient( + 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 = schema.Schema() + + # get arguments that satisfy an http rule for this method + sample_request = {"name": "projects/sample1/schemas/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + revision_id="revision_id_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = schema.Schema.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.rollback_schema(**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=projects/*/schemas/*}:rollback" % client.transport._host, + args[1], + ) + + +def test_rollback_schema_rest_flattened_error(transport: str = "rest"): + client = SchemaServiceClient( + 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.rollback_schema( + schema.RollbackSchemaRequest(), + name="name_value", + revision_id="revision_id_value", + ) + + +def test_rollback_schema_rest_error(): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + schema.DeleteSchemaRevisionRequest, + dict, + ], +) +def test_delete_schema_revision_rest(request_type): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/schemas/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 = schema.Schema( + name="name_value", + type_=schema.Schema.Type.PROTOCOL_BUFFER, + definition="definition_value", + revision_id="revision_id_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = schema.Schema.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_schema_revision(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, schema.Schema) + assert response.name == "name_value" + assert response.type_ == schema.Schema.Type.PROTOCOL_BUFFER + assert response.definition == "definition_value" + assert response.revision_id == "revision_id_value" + + +def test_delete_schema_revision_rest_required_fields( + request_type=schema.DeleteSchemaRevisionRequest, +): + transport_class = transports.SchemaServiceRestTransport + + 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, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_schema_revision._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() + ).delete_schema_revision._get_unset_required_fields(jsonified_request) + # Check that path parameters and body parameters are not mixing in. + assert not set(unset_fields) - set(("revision_id",)) + 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 = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = schema.Schema() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = schema.Schema.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_schema_revision(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_schema_revision_rest_unset_required_fields(): + transport = transports.SchemaServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_schema_revision._get_unset_required_fields({}) + assert set(unset_fields) == (set(("revisionId",)) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_schema_revision_rest_interceptors(null_interceptor): + transport = transports.SchemaServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SchemaServiceRestInterceptor(), + ) + client = SchemaServiceClient(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.SchemaServiceRestInterceptor, "post_delete_schema_revision" + ) as post, mock.patch.object( + transports.SchemaServiceRestInterceptor, "pre_delete_schema_revision" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = schema.DeleteSchemaRevisionRequest.pb( + schema.DeleteSchemaRevisionRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = schema.Schema.to_json(schema.Schema()) + + request = schema.DeleteSchemaRevisionRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = schema.Schema() + + client.delete_schema_revision( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_delete_schema_revision_rest_bad_request( + transport: str = "rest", request_type=schema.DeleteSchemaRevisionRequest +): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/schemas/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_schema_revision(request) + + +def test_delete_schema_revision_rest_flattened(): + client = SchemaServiceClient( + 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 = schema.Schema() + + # get arguments that satisfy an http rule for this method + sample_request = {"name": "projects/sample1/schemas/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + revision_id="revision_id_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = schema.Schema.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_schema_revision(**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=projects/*/schemas/*}:deleteRevision" % client.transport._host, + args[1], + ) + + +def test_delete_schema_revision_rest_flattened_error(transport: str = "rest"): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_schema_revision( + schema.DeleteSchemaRevisionRequest(), + name="name_value", + revision_id="revision_id_value", + ) + + +def test_delete_schema_revision_rest_error(): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + schema.DeleteSchemaRequest, + dict, + ], +) +def test_delete_schema_rest(request_type): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/schemas/sample2"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_schema(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_schema_rest_required_fields(request_type=schema.DeleteSchemaRequest): + transport_class = transports.SchemaServiceRestTransport + + 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, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_schema._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() + ).delete_schema._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 = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_schema(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_schema_rest_unset_required_fields(): + transport = transports.SchemaServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_schema._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("name",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_schema_rest_interceptors(null_interceptor): + transport = transports.SchemaServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SchemaServiceRestInterceptor(), + ) + client = SchemaServiceClient(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.SchemaServiceRestInterceptor, "pre_delete_schema" + ) as pre: + pre.assert_not_called() + pb_message = schema.DeleteSchemaRequest.pb(schema.DeleteSchemaRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = schema.DeleteSchemaRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_schema( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_schema_rest_bad_request( + transport: str = "rest", request_type=schema.DeleteSchemaRequest +): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/schemas/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_schema(request) + + +def test_delete_schema_rest_flattened(): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = {"name": "projects/sample1/schemas/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_schema(**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=projects/*/schemas/*}" % client.transport._host, args[1] + ) + + +def test_delete_schema_rest_flattened_error(transport: str = "rest"): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_schema( + schema.DeleteSchemaRequest(), + name="name_value", + ) + + +def test_delete_schema_rest_error(): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + gp_schema.ValidateSchemaRequest, + dict, + ], +) +def test_validate_schema_rest(request_type): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/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 = gp_schema.ValidateSchemaResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gp_schema.ValidateSchemaResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.validate_schema(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, gp_schema.ValidateSchemaResponse) + + +def test_validate_schema_rest_required_fields( + request_type=gp_schema.ValidateSchemaRequest, +): + transport_class = transports.SchemaServiceRestTransport + + 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, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).validate_schema._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() + ).validate_schema._get_unset_required_fields(jsonified_request) + 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 = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = gp_schema.ValidateSchemaResponse() + # 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 + + pb_return_value = gp_schema.ValidateSchemaResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.validate_schema(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_validate_schema_rest_unset_required_fields(): + transport = transports.SchemaServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.validate_schema._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "parent", + "schema", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_validate_schema_rest_interceptors(null_interceptor): + transport = transports.SchemaServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SchemaServiceRestInterceptor(), + ) + client = SchemaServiceClient(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.SchemaServiceRestInterceptor, "post_validate_schema" + ) as post, mock.patch.object( + transports.SchemaServiceRestInterceptor, "pre_validate_schema" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = gp_schema.ValidateSchemaRequest.pb( + gp_schema.ValidateSchemaRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = gp_schema.ValidateSchemaResponse.to_json( + gp_schema.ValidateSchemaResponse() + ) + + request = gp_schema.ValidateSchemaRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = gp_schema.ValidateSchemaResponse() + + client.validate_schema( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_validate_schema_rest_bad_request( + transport: str = "rest", request_type=gp_schema.ValidateSchemaRequest +): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.validate_schema(request) + + +def test_validate_schema_rest_flattened(): + client = SchemaServiceClient( + 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 = gp_schema.ValidateSchemaResponse() + + # get arguments that satisfy an http rule for this method + sample_request = {"parent": "projects/sample1"} + + # get truthy value for each flattened field + mock_args = dict( + parent="parent_value", + schema=gp_schema.Schema(name="name_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = gp_schema.ValidateSchemaResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.validate_schema(**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=projects/*}/schemas:validate" % client.transport._host, + args[1], + ) + + +def test_validate_schema_rest_flattened_error(transport: str = "rest"): + client = SchemaServiceClient( + 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.validate_schema( + gp_schema.ValidateSchemaRequest(), + parent="parent_value", + schema=gp_schema.Schema(name="name_value"), + ) + + +def test_validate_schema_rest_error(): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + schema.ValidateMessageRequest, + dict, + ], +) +def test_validate_message_rest(request_type): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/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 = schema.ValidateMessageResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = schema.ValidateMessageResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.validate_message(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, schema.ValidateMessageResponse) + + +def test_validate_message_rest_required_fields( + request_type=schema.ValidateMessageRequest, +): + transport_class = transports.SchemaServiceRestTransport + + 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, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).validate_message._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() + ).validate_message._get_unset_required_fields(jsonified_request) + 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 = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = schema.ValidateMessageResponse() + # 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 + + pb_return_value = schema.ValidateMessageResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.validate_message(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_validate_message_rest_unset_required_fields(): + transport = transports.SchemaServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.validate_message._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("parent",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_validate_message_rest_interceptors(null_interceptor): + transport = transports.SchemaServiceRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SchemaServiceRestInterceptor(), + ) + client = SchemaServiceClient(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.SchemaServiceRestInterceptor, "post_validate_message" + ) as post, mock.patch.object( + transports.SchemaServiceRestInterceptor, "pre_validate_message" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = schema.ValidateMessageRequest.pb(schema.ValidateMessageRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = schema.ValidateMessageResponse.to_json( + schema.ValidateMessageResponse() + ) + + request = schema.ValidateMessageRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = schema.ValidateMessageResponse() + + client.validate_message( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_validate_message_rest_bad_request( + transport: str = "rest", request_type=schema.ValidateMessageRequest +): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.validate_message(request) + + +def test_validate_message_rest_error(): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.SchemaServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.SchemaServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SchemaServiceClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.SchemaServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = SchemaServiceClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = SchemaServiceClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.SchemaServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SchemaServiceClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.SchemaServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = SchemaServiceClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.SchemaServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.SchemaServiceGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.SchemaServiceGrpcTransport, + transports.SchemaServiceGrpcAsyncIOTransport, + transports.SchemaServiceRestTransport, + ], +) +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() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = SchemaServiceClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.SchemaServiceGrpcTransport, + ) + + +def test_schema_service_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.SchemaServiceTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_schema_service_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.pubsub_v1.services.schema_service.transports.SchemaServiceTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.SchemaServiceTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "create_schema", + "get_schema", + "list_schemas", + "list_schema_revisions", + "commit_schema", + "rollback_schema", + "delete_schema_revision", + "delete_schema", + "validate_schema", + "validate_message", + "set_iam_policy", + "get_iam_policy", + "test_iam_permissions", + ) + 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_schema_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.pubsub_v1.services.schema_service.transports.SchemaServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (ga_credentials.AnonymousCredentials(), None) + transport = transports.SchemaServiceTransport( + 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/pubsub", + ), + quota_project_id="octopus", + ) + + +def test_schema_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.pubsub_v1.services.schema_service.transports.SchemaServiceTransport._prep_wrapped_messages" + ) as Transport: Transport.return_value = None adc.return_value = (ga_credentials.AnonymousCredentials(), None) transport = transports.SchemaServiceTransport() @@ -3668,6 +6491,7 @@ def test_schema_service_transport_auth_adc(transport_class): [ transports.SchemaServiceGrpcTransport, transports.SchemaServiceGrpcAsyncIOTransport, + transports.SchemaServiceRestTransport, ], ) def test_schema_service_transport_auth_gdch_credentials(transport_class): @@ -3772,11 +6596,23 @@ def test_schema_service_grpc_transport_client_cert_source_for_mtls(transport_cla ) +def test_schema_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.SchemaServiceRestTransport( + 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_schema_service_host_no_port(transport_name): @@ -3787,7 +6623,11 @@ def test_schema_service_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("pubsub.googleapis.com:443") + assert client.transport._host == ( + "pubsub.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://pubsub.googleapis.com" + ) @pytest.mark.parametrize( @@ -3795,6 +6635,7 @@ def test_schema_service_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_schema_service_host_with_port(transport_name): @@ -3805,7 +6646,60 @@ def test_schema_service_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("pubsub.googleapis.com:8000") + assert client.transport._host == ( + "pubsub.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://pubsub.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_schema_service_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = SchemaServiceClient( + credentials=creds1, + transport=transport_name, + ) + client2 = SchemaServiceClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.create_schema._session + session2 = client2.transport.create_schema._session + assert session1 != session2 + session1 = client1.transport.get_schema._session + session2 = client2.transport.get_schema._session + assert session1 != session2 + session1 = client1.transport.list_schemas._session + session2 = client2.transport.list_schemas._session + assert session1 != session2 + session1 = client1.transport.list_schema_revisions._session + session2 = client2.transport.list_schema_revisions._session + assert session1 != session2 + session1 = client1.transport.commit_schema._session + session2 = client2.transport.commit_schema._session + assert session1 != session2 + session1 = client1.transport.rollback_schema._session + session2 = client2.transport.rollback_schema._session + assert session1 != session2 + session1 = client1.transport.delete_schema_revision._session + session2 = client2.transport.delete_schema_revision._session + assert session1 != session2 + session1 = client1.transport.delete_schema._session + session2 = client2.transport.delete_schema._session + assert session1 != session2 + session1 = client1.transport.validate_schema._session + session2 = client2.transport.validate_schema._session + assert session1 != session2 + session1 = client1.transport.validate_message._session + session2 = client2.transport.validate_message._session + assert session1 != session2 def test_schema_service_grpc_transport_channel(): @@ -4099,6 +6993,180 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_iam_policy_rest_bad_request( + transport: str = "rest", request_type=iam_policy_pb2.GetIamPolicyRequest +): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"resource": "projects/sample1/topics/sample2"}, request + ) + + # 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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_iam_policy(request) + + +@pytest.mark.parametrize( + "request_type", + [ + iam_policy_pb2.GetIamPolicyRequest, + dict, + ], +) +def test_get_iam_policy_rest(request_type): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"resource": "projects/sample1/topics/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 = policy_pb2.Policy() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_iam_policy(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, policy_pb2.Policy) + + +def test_set_iam_policy_rest_bad_request( + transport: str = "rest", request_type=iam_policy_pb2.SetIamPolicyRequest +): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"resource": "projects/sample1/topics/sample2"}, request + ) + + # 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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.set_iam_policy(request) + + +@pytest.mark.parametrize( + "request_type", + [ + iam_policy_pb2.SetIamPolicyRequest, + dict, + ], +) +def test_set_iam_policy_rest(request_type): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"resource": "projects/sample1/topics/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 = policy_pb2.Policy() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.set_iam_policy(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, policy_pb2.Policy) + + +def test_test_iam_permissions_rest_bad_request( + transport: str = "rest", request_type=iam_policy_pb2.TestIamPermissionsRequest +): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"resource": "projects/sample1/subscriptions/sample2"}, request + ) + + # 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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.test_iam_permissions(request) + + +@pytest.mark.parametrize( + "request_type", + [ + iam_policy_pb2.TestIamPermissionsRequest, + dict, + ], +) +def test_test_iam_permissions_rest(request_type): + client = SchemaServiceClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"resource": "projects/sample1/subscriptions/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 = iam_policy_pb2.TestIamPermissionsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.test_iam_permissions(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, iam_policy_pb2.TestIamPermissionsResponse) + + def test_set_iam_policy(transport: str = "grpc"): client = SchemaServiceClient( credentials=ga_credentials.AnonymousCredentials(), @@ -4609,6 +7677,7 @@ async def test_test_iam_permissions_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -4626,6 +7695,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: diff --git a/tests/unit/gapic/pubsub_v1/test_subscriber.py b/tests/unit/gapic/pubsub_v1/test_subscriber.py index 3be5857f3..0a8ebfc99 100644 --- a/tests/unit/gapic/pubsub_v1/test_subscriber.py +++ b/tests/unit/gapic/pubsub_v1/test_subscriber.py @@ -106,6 +106,7 @@ def test__get_default_mtls_endpoint(): [ (SubscriberClient, "grpc"), (SubscriberAsyncClient, "grpc_asyncio"), + (SubscriberClient, "rest"), ], ) def test_subscriber_client_from_service_account_info(client_class, transport_name): @@ -119,7 +120,11 @@ def test_subscriber_client_from_service_account_info(client_class, transport_nam assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("pubsub.googleapis.com:443") + assert client.transport._host == ( + "pubsub.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://pubsub.googleapis.com" + ) @pytest.mark.parametrize( @@ -127,6 +132,7 @@ def test_subscriber_client_from_service_account_info(client_class, transport_nam [ (transports.SubscriberGrpcTransport, "grpc"), (transports.SubscriberGrpcAsyncIOTransport, "grpc_asyncio"), + (transports.SubscriberRestTransport, "rest"), ], ) def test_subscriber_client_service_account_always_use_jwt( @@ -152,6 +158,7 @@ def test_subscriber_client_service_account_always_use_jwt( [ (SubscriberClient, "grpc"), (SubscriberAsyncClient, "grpc_asyncio"), + (SubscriberClient, "rest"), ], ) def test_subscriber_client_from_service_account_file(client_class, transport_name): @@ -172,13 +179,18 @@ def test_subscriber_client_from_service_account_file(client_class, transport_nam assert client.transport._credentials == creds assert isinstance(client, client_class) - assert client.transport._host == ("pubsub.googleapis.com:443") + assert client.transport._host == ( + "pubsub.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://pubsub.googleapis.com" + ) def test_subscriber_client_get_transport_class(): transport = SubscriberClient.get_transport_class() available_transports = [ transports.SubscriberGrpcTransport, + transports.SubscriberRestTransport, ] assert transport in available_transports @@ -195,6 +207,7 @@ def test_subscriber_client_get_transport_class(): transports.SubscriberGrpcAsyncIOTransport, "grpc_asyncio", ), + (SubscriberClient, transports.SubscriberRestTransport, "rest"), ], ) @mock.patch.object( @@ -338,6 +351,8 @@ def test_subscriber_client_client_options( "grpc_asyncio", "false", ), + (SubscriberClient, transports.SubscriberRestTransport, "rest", "true"), + (SubscriberClient, transports.SubscriberRestTransport, "rest", "false"), ], ) @mock.patch.object( @@ -531,6 +546,7 @@ def test_subscriber_client_get_mtls_endpoint_and_cert_source(client_class): transports.SubscriberGrpcAsyncIOTransport, "grpc_asyncio", ), + (SubscriberClient, transports.SubscriberRestTransport, "rest"), ], ) def test_subscriber_client_client_options_scopes( @@ -566,6 +582,7 @@ def test_subscriber_client_client_options_scopes( "grpc_asyncio", grpc_helpers_async, ), + (SubscriberClient, transports.SubscriberRestTransport, "rest", None), ], ) def test_subscriber_client_client_options_credentials_file( @@ -4740,177 +4757,4329 @@ async def test_seek_field_headers_async(): ) in kw["metadata"] -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.SubscriberGrpcTransport( +@pytest.mark.parametrize( + "request_type", + [ + pubsub.Subscription, + dict, + ], +) +def test_create_subscription_rest(request_type): + client = SubscriberClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - with pytest.raises(ValueError): - client = SubscriberClient( - credentials=ga_credentials.AnonymousCredentials(), - transport=transport, + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/subscriptions/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 = pubsub.Subscription( + name="name_value", + topic="topic_value", + ack_deadline_seconds=2066, + retain_acked_messages=True, + enable_message_ordering=True, + filter="filter_value", + detached=True, + enable_exactly_once_delivery=True, + state=pubsub.Subscription.State.ACTIVE, ) - # It is an error to provide a credentials file and a transport instance. - transport = transports.SubscriberGrpcTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) - with pytest.raises(ValueError): - client = SubscriberClient( - client_options={"credentials_file": "credentials.json"}, - transport=transport, + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.Subscription.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_subscription(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pubsub.Subscription) + assert response.name == "name_value" + assert response.topic == "topic_value" + assert response.ack_deadline_seconds == 2066 + assert response.retain_acked_messages is True + assert response.enable_message_ordering is True + assert response.filter == "filter_value" + assert response.detached is True + assert response.enable_exactly_once_delivery is True + assert response.state == pubsub.Subscription.State.ACTIVE + + +def test_create_subscription_rest_required_fields(request_type=pubsub.Subscription): + transport_class = transports.SubscriberRestTransport + + request_init = {} + request_init["name"] = "" + request_init["topic"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, ) + ) - # It is an error to provide an api_key and a transport instance. - transport = transports.SubscriberGrpcTransport( + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_subscription._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" + jsonified_request["topic"] = "topic_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_subscription._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" + assert "topic" in jsonified_request + assert jsonified_request["topic"] == "topic_value" + + client = SubscriberClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = pubsub.Subscription() + # 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": "put", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = pubsub.Subscription.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_subscription(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_subscription_rest_unset_required_fields(): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials ) - options = client_options.ClientOptions() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = SubscriberClient( - client_options=options, - transport=transport, - ) - # It is an error to provide an api_key and a credential. - options = mock.Mock() - options.api_key = "api_key" - with pytest.raises(ValueError): - client = SubscriberClient( - client_options=options, credentials=ga_credentials.AnonymousCredentials() + unset_fields = transport.create_subscription._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "name", + "topic", + ) ) + ) - # It is an error to provide scopes and a transport instance. - transport = transports.SubscriberGrpcTransport( + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_subscription_rest_interceptors(null_interceptor): + transport = transports.SubscriberRestTransport( credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SubscriberRestInterceptor(), ) - with pytest.raises(ValueError): - client = SubscriberClient( - client_options={"scopes": ["1", "2"]}, - transport=transport, + client = SubscriberClient(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.SubscriberRestInterceptor, "post_create_subscription" + ) as post, mock.patch.object( + transports.SubscriberRestInterceptor, "pre_create_subscription" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = pubsub.Subscription.pb(pubsub.Subscription()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = pubsub.Subscription.to_json(pubsub.Subscription()) + + request = pubsub.Subscription() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = pubsub.Subscription() + + client.create_subscription( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], ) + pre.assert_called_once() + post.assert_called_once() -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.SubscriberGrpcTransport( + +def test_create_subscription_rest_bad_request( + transport: str = "rest", request_type=pubsub.Subscription +): + client = SubscriberClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - client = SubscriberClient(transport=transport) - assert client.transport is transport + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/subscriptions/sample2"} + request = request_type(**request_init) -def test_transport_get_channel(): - # A client may be instantiated with a custom transport instance. - transport = transports.SubscriberGrpcTransport( + # 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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_subscription(request) + + +def test_create_subscription_rest_flattened(): + client = SubscriberClient( credentials=ga_credentials.AnonymousCredentials(), + transport="rest", ) - channel = transport.grpc_channel - assert channel - transport = transports.SubscriberGrpcAsyncIOTransport( + # 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 = pubsub.Subscription() + + # get arguments that satisfy an http rule for this method + sample_request = {"name": "projects/sample1/subscriptions/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + topic="topic_value", + push_config=pubsub.PushConfig(push_endpoint="push_endpoint_value"), + ack_deadline_seconds=2066, + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.Subscription.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_subscription(**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=projects/*/subscriptions/*}" % client.transport._host, args[1] + ) + + +def test_create_subscription_rest_flattened_error(transport: str = "rest"): + client = SubscriberClient( credentials=ga_credentials.AnonymousCredentials(), + transport=transport, ) - channel = transport.grpc_channel - assert channel + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_subscription( + pubsub.Subscription(), + name="name_value", + topic="topic_value", + push_config=pubsub.PushConfig(push_endpoint="push_endpoint_value"), + ack_deadline_seconds=2066, + ) -@pytest.mark.parametrize( - "transport_class", - [ - transports.SubscriberGrpcTransport, - transports.SubscriberGrpcAsyncIOTransport, - ], -) -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_create_subscription_rest_error(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) @pytest.mark.parametrize( - "transport_name", + "request_type", [ - "grpc", + pubsub.GetSubscriptionRequest, + dict, ], ) -def test_transport_kind(transport_name): - transport = SubscriberClient.get_transport_class(transport_name)( - credentials=ga_credentials.AnonymousCredentials(), - ) - assert transport.kind == transport_name - - -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. +def test_get_subscription_rest(request_type): client = SubscriberClient( credentials=ga_credentials.AnonymousCredentials(), - ) - assert isinstance( - client.transport, - transports.SubscriberGrpcTransport, + transport="rest", ) + # send a request that will satisfy transcoding + request_init = {"subscription": "projects/sample1/subscriptions/sample2"} + request = request_type(**request_init) -def test_subscriber_base_transport_error(): - # Passing both a credentials object and credentials_file should raise an error - with pytest.raises(core_exceptions.DuplicateCredentialArgs): - transport = transports.SubscriberTransport( - credentials=ga_credentials.AnonymousCredentials(), - credentials_file="credentials.json", + # 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 = pubsub.Subscription( + name="name_value", + topic="topic_value", + ack_deadline_seconds=2066, + retain_acked_messages=True, + enable_message_ordering=True, + filter="filter_value", + detached=True, + enable_exactly_once_delivery=True, + state=pubsub.Subscription.State.ACTIVE, ) + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.Subscription.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) -def test_subscriber_base_transport(): - # Instantiate the base transport. - with mock.patch( - "google.pubsub_v1.services.subscriber.transports.SubscriberTransport.__init__" - ) as Transport: - Transport.return_value = None - transport = transports.SubscriberTransport( - credentials=ga_credentials.AnonymousCredentials(), - ) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_subscription(request) - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ( - "create_subscription", - "get_subscription", - "update_subscription", - "list_subscriptions", - "delete_subscription", - "modify_ack_deadline", - "acknowledge", - "pull", - "streaming_pull", - "modify_push_config", - "get_snapshot", - "list_snapshots", - "create_snapshot", - "update_snapshot", - "delete_snapshot", - "seek", - "set_iam_policy", - "get_iam_policy", - "test_iam_permissions", + # Establish that the response is the type that we expect. + assert isinstance(response, pubsub.Subscription) + assert response.name == "name_value" + assert response.topic == "topic_value" + assert response.ack_deadline_seconds == 2066 + assert response.retain_acked_messages is True + assert response.enable_message_ordering is True + assert response.filter == "filter_value" + assert response.detached is True + assert response.enable_exactly_once_delivery is True + assert response.state == pubsub.Subscription.State.ACTIVE + + +def test_get_subscription_rest_required_fields( + request_type=pubsub.GetSubscriptionRequest, +): + transport_class = transports.SubscriberRestTransport + + request_init = {} + request_init["subscription"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) ) - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) - with pytest.raises(NotImplementedError): - transport.close() + # verify fields with default values are dropped - # Catch all for all remaining methods and properties - remainder = [ - "kind", - ] - for r in remainder: - with pytest.raises(NotImplementedError): - getattr(transport, r)() + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_subscription._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["subscription"] = "subscription_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_subscription._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "subscription" in jsonified_request + assert jsonified_request["subscription"] == "subscription_value" + + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = pubsub.Subscription() + # 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 + + pb_return_value = pubsub.Subscription.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_subscription(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_subscription_rest_unset_required_fields(): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_subscription._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("subscription",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_subscription_rest_interceptors(null_interceptor): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SubscriberRestInterceptor(), + ) + client = SubscriberClient(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.SubscriberRestInterceptor, "post_get_subscription" + ) as post, mock.patch.object( + transports.SubscriberRestInterceptor, "pre_get_subscription" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = pubsub.GetSubscriptionRequest.pb(pubsub.GetSubscriptionRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = pubsub.Subscription.to_json(pubsub.Subscription()) + + request = pubsub.GetSubscriptionRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = pubsub.Subscription() + + client.get_subscription( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_subscription_rest_bad_request( + transport: str = "rest", request_type=pubsub.GetSubscriptionRequest +): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"subscription": "projects/sample1/subscriptions/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_subscription(request) + + +def test_get_subscription_rest_flattened(): + client = SubscriberClient( + 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 = pubsub.Subscription() + + # get arguments that satisfy an http rule for this method + sample_request = {"subscription": "projects/sample1/subscriptions/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + subscription="subscription_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.Subscription.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_subscription(**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/{subscription=projects/*/subscriptions/*}" % client.transport._host, + args[1], + ) + + +def test_get_subscription_rest_flattened_error(transport: str = "rest"): + client = SubscriberClient( + 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_subscription( + pubsub.GetSubscriptionRequest(), + subscription="subscription_value", + ) + + +def test_get_subscription_rest_error(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.UpdateSubscriptionRequest, + dict, + ], +) +def test_update_subscription_rest(request_type): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"subscription": {"name": "projects/sample1/subscriptions/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 = pubsub.Subscription( + name="name_value", + topic="topic_value", + ack_deadline_seconds=2066, + retain_acked_messages=True, + enable_message_ordering=True, + filter="filter_value", + detached=True, + enable_exactly_once_delivery=True, + state=pubsub.Subscription.State.ACTIVE, + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.Subscription.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_subscription(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pubsub.Subscription) + assert response.name == "name_value" + assert response.topic == "topic_value" + assert response.ack_deadline_seconds == 2066 + assert response.retain_acked_messages is True + assert response.enable_message_ordering is True + assert response.filter == "filter_value" + assert response.detached is True + assert response.enable_exactly_once_delivery is True + assert response.state == pubsub.Subscription.State.ACTIVE + + +def test_update_subscription_rest_required_fields( + request_type=pubsub.UpdateSubscriptionRequest, +): + transport_class = transports.SubscriberRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_subscription._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_subscription._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = pubsub.Subscription() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = pubsub.Subscription.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_subscription(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_subscription_rest_unset_required_fields(): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_subscription._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "subscription", + "updateMask", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_subscription_rest_interceptors(null_interceptor): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SubscriberRestInterceptor(), + ) + client = SubscriberClient(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.SubscriberRestInterceptor, "post_update_subscription" + ) as post, mock.patch.object( + transports.SubscriberRestInterceptor, "pre_update_subscription" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = pubsub.UpdateSubscriptionRequest.pb( + pubsub.UpdateSubscriptionRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = pubsub.Subscription.to_json(pubsub.Subscription()) + + request = pubsub.UpdateSubscriptionRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = pubsub.Subscription() + + client.update_subscription( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_subscription_rest_bad_request( + transport: str = "rest", request_type=pubsub.UpdateSubscriptionRequest +): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"subscription": {"name": "projects/sample1/subscriptions/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_subscription(request) + + +def test_update_subscription_rest_flattened(): + client = SubscriberClient( + 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 = pubsub.Subscription() + + # get arguments that satisfy an http rule for this method + sample_request = { + "subscription": {"name": "projects/sample1/subscriptions/sample2"} + } + + # get truthy value for each flattened field + mock_args = dict( + subscription=pubsub.Subscription(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.Subscription.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_subscription(**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/{subscription.name=projects/*/subscriptions/*}" + % client.transport._host, + args[1], + ) + + +def test_update_subscription_rest_flattened_error(transport: str = "rest"): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_subscription( + pubsub.UpdateSubscriptionRequest(), + subscription=pubsub.Subscription(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_subscription_rest_error(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.ListSubscriptionsRequest, + dict, + ], +) +def test_list_subscriptions_rest(request_type): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"project": "projects/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 = pubsub.ListSubscriptionsResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.ListSubscriptionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_subscriptions(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListSubscriptionsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_subscriptions_rest_required_fields( + request_type=pubsub.ListSubscriptionsRequest, +): + transport_class = transports.SubscriberRestTransport + + request_init = {} + request_init["project"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_subscriptions._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["project"] = "project_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_subscriptions._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 "project" in jsonified_request + assert jsonified_request["project"] == "project_value" + + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = pubsub.ListSubscriptionsResponse() + # 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 + + pb_return_value = pubsub.ListSubscriptionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_subscriptions(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_subscriptions_rest_unset_required_fields(): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_subscriptions._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("project",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_subscriptions_rest_interceptors(null_interceptor): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SubscriberRestInterceptor(), + ) + client = SubscriberClient(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.SubscriberRestInterceptor, "post_list_subscriptions" + ) as post, mock.patch.object( + transports.SubscriberRestInterceptor, "pre_list_subscriptions" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = pubsub.ListSubscriptionsRequest.pb( + pubsub.ListSubscriptionsRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = pubsub.ListSubscriptionsResponse.to_json( + pubsub.ListSubscriptionsResponse() + ) + + request = pubsub.ListSubscriptionsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = pubsub.ListSubscriptionsResponse() + + client.list_subscriptions( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_list_subscriptions_rest_bad_request( + transport: str = "rest", request_type=pubsub.ListSubscriptionsRequest +): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"project": "projects/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_subscriptions(request) + + +def test_list_subscriptions_rest_flattened(): + client = SubscriberClient( + 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 = pubsub.ListSubscriptionsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = {"project": "projects/sample1"} + + # get truthy value for each flattened field + mock_args = dict( + project="project_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.ListSubscriptionsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_subscriptions(**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/{project=projects/*}/subscriptions" % client.transport._host, args[1] + ) + + +def test_list_subscriptions_rest_flattened_error(transport: str = "rest"): + client = SubscriberClient( + 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_subscriptions( + pubsub.ListSubscriptionsRequest(), + project="project_value", + ) + + +def test_list_subscriptions_rest_pager(transport: str = "rest"): + client = SubscriberClient( + 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 = ( + pubsub.ListSubscriptionsResponse( + subscriptions=[ + pubsub.Subscription(), + pubsub.Subscription(), + pubsub.Subscription(), + ], + next_page_token="abc", + ), + pubsub.ListSubscriptionsResponse( + subscriptions=[], + next_page_token="def", + ), + pubsub.ListSubscriptionsResponse( + subscriptions=[ + pubsub.Subscription(), + ], + next_page_token="ghi", + ), + pubsub.ListSubscriptionsResponse( + subscriptions=[ + pubsub.Subscription(), + pubsub.Subscription(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(pubsub.ListSubscriptionsResponse.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 = {"project": "projects/sample1"} + + pager = client.list_subscriptions(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, pubsub.Subscription) for i in results) + + pages = list(client.list_subscriptions(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.DeleteSubscriptionRequest, + dict, + ], +) +def test_delete_subscription_rest(request_type): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"subscription": "projects/sample1/subscriptions/sample2"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_subscription(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_subscription_rest_required_fields( + request_type=pubsub.DeleteSubscriptionRequest, +): + transport_class = transports.SubscriberRestTransport + + request_init = {} + request_init["subscription"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_subscription._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["subscription"] = "subscription_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_subscription._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "subscription" in jsonified_request + assert jsonified_request["subscription"] == "subscription_value" + + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_subscription(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_subscription_rest_unset_required_fields(): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_subscription._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("subscription",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_subscription_rest_interceptors(null_interceptor): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SubscriberRestInterceptor(), + ) + client = SubscriberClient(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.SubscriberRestInterceptor, "pre_delete_subscription" + ) as pre: + pre.assert_not_called() + pb_message = pubsub.DeleteSubscriptionRequest.pb( + pubsub.DeleteSubscriptionRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = pubsub.DeleteSubscriptionRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_subscription( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_subscription_rest_bad_request( + transport: str = "rest", request_type=pubsub.DeleteSubscriptionRequest +): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"subscription": "projects/sample1/subscriptions/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_subscription(request) + + +def test_delete_subscription_rest_flattened(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = {"subscription": "projects/sample1/subscriptions/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + subscription="subscription_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_subscription(**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/{subscription=projects/*/subscriptions/*}" % client.transport._host, + args[1], + ) + + +def test_delete_subscription_rest_flattened_error(transport: str = "rest"): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_subscription( + pubsub.DeleteSubscriptionRequest(), + subscription="subscription_value", + ) + + +def test_delete_subscription_rest_error(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.ModifyAckDeadlineRequest, + dict, + ], +) +def test_modify_ack_deadline_rest(request_type): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"subscription": "projects/sample1/subscriptions/sample2"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.modify_ack_deadline(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_modify_ack_deadline_rest_required_fields( + request_type=pubsub.ModifyAckDeadlineRequest, +): + transport_class = transports.SubscriberRestTransport + + request_init = {} + request_init["subscription"] = "" + request_init["ack_ids"] = "" + request_init["ack_deadline_seconds"] = 0 + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).modify_ack_deadline._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["subscription"] = "subscription_value" + jsonified_request["ackIds"] = "ack_ids_value" + jsonified_request["ackDeadlineSeconds"] = 2066 + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).modify_ack_deadline._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "subscription" in jsonified_request + assert jsonified_request["subscription"] == "subscription_value" + assert "ackIds" in jsonified_request + assert jsonified_request["ackIds"] == "ack_ids_value" + assert "ackDeadlineSeconds" in jsonified_request + assert jsonified_request["ackDeadlineSeconds"] == 2066 + + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.modify_ack_deadline(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_modify_ack_deadline_rest_unset_required_fields(): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.modify_ack_deadline._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "subscription", + "ackIds", + "ackDeadlineSeconds", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_modify_ack_deadline_rest_interceptors(null_interceptor): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SubscriberRestInterceptor(), + ) + client = SubscriberClient(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.SubscriberRestInterceptor, "pre_modify_ack_deadline" + ) as pre: + pre.assert_not_called() + pb_message = pubsub.ModifyAckDeadlineRequest.pb( + pubsub.ModifyAckDeadlineRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = pubsub.ModifyAckDeadlineRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.modify_ack_deadline( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_modify_ack_deadline_rest_bad_request( + transport: str = "rest", request_type=pubsub.ModifyAckDeadlineRequest +): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"subscription": "projects/sample1/subscriptions/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.modify_ack_deadline(request) + + +def test_modify_ack_deadline_rest_flattened(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = {"subscription": "projects/sample1/subscriptions/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + subscription="subscription_value", + ack_ids=["ack_ids_value"], + ack_deadline_seconds=2066, + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.modify_ack_deadline(**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/{subscription=projects/*/subscriptions/*}:modifyAckDeadline" + % client.transport._host, + args[1], + ) + + +def test_modify_ack_deadline_rest_flattened_error(transport: str = "rest"): + client = SubscriberClient( + 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.modify_ack_deadline( + pubsub.ModifyAckDeadlineRequest(), + subscription="subscription_value", + ack_ids=["ack_ids_value"], + ack_deadline_seconds=2066, + ) + + +def test_modify_ack_deadline_rest_error(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.AcknowledgeRequest, + dict, + ], +) +def test_acknowledge_rest(request_type): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"subscription": "projects/sample1/subscriptions/sample2"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.acknowledge(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_acknowledge_rest_required_fields(request_type=pubsub.AcknowledgeRequest): + transport_class = transports.SubscriberRestTransport + + request_init = {} + request_init["subscription"] = "" + request_init["ack_ids"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).acknowledge._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["subscription"] = "subscription_value" + jsonified_request["ackIds"] = "ack_ids_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).acknowledge._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "subscription" in jsonified_request + assert jsonified_request["subscription"] == "subscription_value" + assert "ackIds" in jsonified_request + assert jsonified_request["ackIds"] == "ack_ids_value" + + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.acknowledge(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_acknowledge_rest_unset_required_fields(): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.acknowledge._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "subscription", + "ackIds", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_acknowledge_rest_interceptors(null_interceptor): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SubscriberRestInterceptor(), + ) + client = SubscriberClient(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.SubscriberRestInterceptor, "pre_acknowledge" + ) as pre: + pre.assert_not_called() + pb_message = pubsub.AcknowledgeRequest.pb(pubsub.AcknowledgeRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = pubsub.AcknowledgeRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.acknowledge( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_acknowledge_rest_bad_request( + transport: str = "rest", request_type=pubsub.AcknowledgeRequest +): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"subscription": "projects/sample1/subscriptions/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.acknowledge(request) + + +def test_acknowledge_rest_flattened(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = {"subscription": "projects/sample1/subscriptions/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + subscription="subscription_value", + ack_ids=["ack_ids_value"], + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.acknowledge(**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/{subscription=projects/*/subscriptions/*}:acknowledge" + % client.transport._host, + args[1], + ) + + +def test_acknowledge_rest_flattened_error(transport: str = "rest"): + client = SubscriberClient( + 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.acknowledge( + pubsub.AcknowledgeRequest(), + subscription="subscription_value", + ack_ids=["ack_ids_value"], + ) + + +def test_acknowledge_rest_error(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.PullRequest, + dict, + ], +) +def test_pull_rest(request_type): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"subscription": "projects/sample1/subscriptions/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 = pubsub.PullResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.PullResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.pull(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pubsub.PullResponse) + + +def test_pull_rest_required_fields(request_type=pubsub.PullRequest): + transport_class = transports.SubscriberRestTransport + + request_init = {} + request_init["subscription"] = "" + request_init["max_messages"] = 0 + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).pull._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["subscription"] = "subscription_value" + jsonified_request["maxMessages"] = 1277 + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).pull._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "subscription" in jsonified_request + assert jsonified_request["subscription"] == "subscription_value" + assert "maxMessages" in jsonified_request + assert jsonified_request["maxMessages"] == 1277 + + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = pubsub.PullResponse() + # 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 + + pb_return_value = pubsub.PullResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.pull(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_pull_rest_unset_required_fields(): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.pull._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "subscription", + "maxMessages", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_pull_rest_interceptors(null_interceptor): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SubscriberRestInterceptor(), + ) + client = SubscriberClient(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.SubscriberRestInterceptor, "post_pull" + ) as post, mock.patch.object( + transports.SubscriberRestInterceptor, "pre_pull" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = pubsub.PullRequest.pb(pubsub.PullRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = pubsub.PullResponse.to_json(pubsub.PullResponse()) + + request = pubsub.PullRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = pubsub.PullResponse() + + client.pull( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_pull_rest_bad_request( + transport: str = "rest", request_type=pubsub.PullRequest +): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"subscription": "projects/sample1/subscriptions/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.pull(request) + + +def test_pull_rest_flattened(): + client = SubscriberClient( + 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 = pubsub.PullResponse() + + # get arguments that satisfy an http rule for this method + sample_request = {"subscription": "projects/sample1/subscriptions/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + subscription="subscription_value", + return_immediately=True, + max_messages=1277, + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.PullResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.pull(**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/{subscription=projects/*/subscriptions/*}:pull" + % client.transport._host, + args[1], + ) + + +def test_pull_rest_flattened_error(transport: str = "rest"): + client = SubscriberClient( + 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.pull( + pubsub.PullRequest(), + subscription="subscription_value", + return_immediately=True, + max_messages=1277, + ) + + +def test_pull_rest_error(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_streaming_pull_rest_no_http_options(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = pubsub.StreamingPullRequest() + requests = [request] + with pytest.raises(RuntimeError): + client.streaming_pull(requests) + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.ModifyPushConfigRequest, + dict, + ], +) +def test_modify_push_config_rest(request_type): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"subscription": "projects/sample1/subscriptions/sample2"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.modify_push_config(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_modify_push_config_rest_required_fields( + request_type=pubsub.ModifyPushConfigRequest, +): + transport_class = transports.SubscriberRestTransport + + request_init = {} + request_init["subscription"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).modify_push_config._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["subscription"] = "subscription_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).modify_push_config._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "subscription" in jsonified_request + assert jsonified_request["subscription"] == "subscription_value" + + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.modify_push_config(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_modify_push_config_rest_unset_required_fields(): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.modify_push_config._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "subscription", + "pushConfig", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_modify_push_config_rest_interceptors(null_interceptor): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SubscriberRestInterceptor(), + ) + client = SubscriberClient(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.SubscriberRestInterceptor, "pre_modify_push_config" + ) as pre: + pre.assert_not_called() + pb_message = pubsub.ModifyPushConfigRequest.pb(pubsub.ModifyPushConfigRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = pubsub.ModifyPushConfigRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.modify_push_config( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_modify_push_config_rest_bad_request( + transport: str = "rest", request_type=pubsub.ModifyPushConfigRequest +): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"subscription": "projects/sample1/subscriptions/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.modify_push_config(request) + + +def test_modify_push_config_rest_flattened(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = {"subscription": "projects/sample1/subscriptions/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + subscription="subscription_value", + push_config=pubsub.PushConfig(push_endpoint="push_endpoint_value"), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.modify_push_config(**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/{subscription=projects/*/subscriptions/*}:modifyPushConfig" + % client.transport._host, + args[1], + ) + + +def test_modify_push_config_rest_flattened_error(transport: str = "rest"): + client = SubscriberClient( + 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.modify_push_config( + pubsub.ModifyPushConfigRequest(), + subscription="subscription_value", + push_config=pubsub.PushConfig(push_endpoint="push_endpoint_value"), + ) + + +def test_modify_push_config_rest_error(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.GetSnapshotRequest, + dict, + ], +) +def test_get_snapshot_rest(request_type): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"snapshot": "projects/sample1/snapshots/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 = pubsub.Snapshot( + name="name_value", + topic="topic_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.Snapshot.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.get_snapshot(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pubsub.Snapshot) + assert response.name == "name_value" + assert response.topic == "topic_value" + + +def test_get_snapshot_rest_required_fields(request_type=pubsub.GetSnapshotRequest): + transport_class = transports.SubscriberRestTransport + + request_init = {} + request_init["snapshot"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_snapshot._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["snapshot"] = "snapshot_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).get_snapshot._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "snapshot" in jsonified_request + assert jsonified_request["snapshot"] == "snapshot_value" + + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = pubsub.Snapshot() + # 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 + + pb_return_value = pubsub.Snapshot.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_snapshot(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_get_snapshot_rest_unset_required_fields(): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.get_snapshot._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("snapshot",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_get_snapshot_rest_interceptors(null_interceptor): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SubscriberRestInterceptor(), + ) + client = SubscriberClient(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.SubscriberRestInterceptor, "post_get_snapshot" + ) as post, mock.patch.object( + transports.SubscriberRestInterceptor, "pre_get_snapshot" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = pubsub.GetSnapshotRequest.pb(pubsub.GetSnapshotRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = pubsub.Snapshot.to_json(pubsub.Snapshot()) + + request = pubsub.GetSnapshotRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = pubsub.Snapshot() + + client.get_snapshot( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_get_snapshot_rest_bad_request( + transport: str = "rest", request_type=pubsub.GetSnapshotRequest +): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"snapshot": "projects/sample1/snapshots/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_snapshot(request) + + +def test_get_snapshot_rest_flattened(): + client = SubscriberClient( + 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 = pubsub.Snapshot() + + # get arguments that satisfy an http rule for this method + sample_request = {"snapshot": "projects/sample1/snapshots/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + snapshot="snapshot_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.Snapshot.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.get_snapshot(**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/{snapshot=projects/*/snapshots/*}" % client.transport._host, args[1] + ) + + +def test_get_snapshot_rest_flattened_error(transport: str = "rest"): + client = SubscriberClient( + 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_snapshot( + pubsub.GetSnapshotRequest(), + snapshot="snapshot_value", + ) + + +def test_get_snapshot_rest_error(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.ListSnapshotsRequest, + dict, + ], +) +def test_list_snapshots_rest(request_type): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"project": "projects/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 = pubsub.ListSnapshotsResponse( + next_page_token="next_page_token_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.ListSnapshotsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.list_snapshots(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListSnapshotsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_snapshots_rest_required_fields(request_type=pubsub.ListSnapshotsRequest): + transport_class = transports.SubscriberRestTransport + + request_init = {} + request_init["project"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_snapshots._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["project"] = "project_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).list_snapshots._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 "project" in jsonified_request + assert jsonified_request["project"] == "project_value" + + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = pubsub.ListSnapshotsResponse() + # 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 + + pb_return_value = pubsub.ListSnapshotsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.list_snapshots(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_list_snapshots_rest_unset_required_fields(): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.list_snapshots._get_unset_required_fields({}) + assert set(unset_fields) == ( + set( + ( + "pageSize", + "pageToken", + ) + ) + & set(("project",)) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_list_snapshots_rest_interceptors(null_interceptor): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SubscriberRestInterceptor(), + ) + client = SubscriberClient(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.SubscriberRestInterceptor, "post_list_snapshots" + ) as post, mock.patch.object( + transports.SubscriberRestInterceptor, "pre_list_snapshots" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = pubsub.ListSnapshotsRequest.pb(pubsub.ListSnapshotsRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = pubsub.ListSnapshotsResponse.to_json( + pubsub.ListSnapshotsResponse() + ) + + request = pubsub.ListSnapshotsRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = pubsub.ListSnapshotsResponse() + + client.list_snapshots( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_list_snapshots_rest_bad_request( + transport: str = "rest", request_type=pubsub.ListSnapshotsRequest +): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"project": "projects/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.list_snapshots(request) + + +def test_list_snapshots_rest_flattened(): + client = SubscriberClient( + 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 = pubsub.ListSnapshotsResponse() + + # get arguments that satisfy an http rule for this method + sample_request = {"project": "projects/sample1"} + + # get truthy value for each flattened field + mock_args = dict( + project="project_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.ListSnapshotsResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.list_snapshots(**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/{project=projects/*}/snapshots" % client.transport._host, args[1] + ) + + +def test_list_snapshots_rest_flattened_error(transport: str = "rest"): + client = SubscriberClient( + 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_snapshots( + pubsub.ListSnapshotsRequest(), + project="project_value", + ) + + +def test_list_snapshots_rest_pager(transport: str = "rest"): + client = SubscriberClient( + 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 = ( + pubsub.ListSnapshotsResponse( + snapshots=[ + pubsub.Snapshot(), + pubsub.Snapshot(), + pubsub.Snapshot(), + ], + next_page_token="abc", + ), + pubsub.ListSnapshotsResponse( + snapshots=[], + next_page_token="def", + ), + pubsub.ListSnapshotsResponse( + snapshots=[ + pubsub.Snapshot(), + ], + next_page_token="ghi", + ), + pubsub.ListSnapshotsResponse( + snapshots=[ + pubsub.Snapshot(), + pubsub.Snapshot(), + ], + ), + ) + # Two responses for two calls + response = response + response + + # Wrap the values into proper Response objs + response = tuple(pubsub.ListSnapshotsResponse.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 = {"project": "projects/sample1"} + + pager = client.list_snapshots(request=sample_request) + + results = list(pager) + assert len(results) == 6 + assert all(isinstance(i, pubsub.Snapshot) for i in results) + + pages = list(client.list_snapshots(request=sample_request).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.CreateSnapshotRequest, + dict, + ], +) +def test_create_snapshot_rest(request_type): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/snapshots/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 = pubsub.Snapshot( + name="name_value", + topic="topic_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.Snapshot.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.create_snapshot(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pubsub.Snapshot) + assert response.name == "name_value" + assert response.topic == "topic_value" + + +def test_create_snapshot_rest_required_fields( + request_type=pubsub.CreateSnapshotRequest, +): + transport_class = transports.SubscriberRestTransport + + request_init = {} + request_init["name"] = "" + request_init["subscription"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_snapshot._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" + jsonified_request["subscription"] = "subscription_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).create_snapshot._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" + assert "subscription" in jsonified_request + assert jsonified_request["subscription"] == "subscription_value" + + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = pubsub.Snapshot() + # 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": "put", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = pubsub.Snapshot.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.create_snapshot(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_create_snapshot_rest_unset_required_fields(): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.create_snapshot._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "name", + "subscription", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_create_snapshot_rest_interceptors(null_interceptor): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SubscriberRestInterceptor(), + ) + client = SubscriberClient(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.SubscriberRestInterceptor, "post_create_snapshot" + ) as post, mock.patch.object( + transports.SubscriberRestInterceptor, "pre_create_snapshot" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = pubsub.CreateSnapshotRequest.pb(pubsub.CreateSnapshotRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = pubsub.Snapshot.to_json(pubsub.Snapshot()) + + request = pubsub.CreateSnapshotRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = pubsub.Snapshot() + + client.create_snapshot( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_create_snapshot_rest_bad_request( + transport: str = "rest", request_type=pubsub.CreateSnapshotRequest +): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"name": "projects/sample1/snapshots/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.create_snapshot(request) + + +def test_create_snapshot_rest_flattened(): + client = SubscriberClient( + 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 = pubsub.Snapshot() + + # get arguments that satisfy an http rule for this method + sample_request = {"name": "projects/sample1/snapshots/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + name="name_value", + subscription="subscription_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.Snapshot.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.create_snapshot(**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=projects/*/snapshots/*}" % client.transport._host, args[1] + ) + + +def test_create_snapshot_rest_flattened_error(transport: str = "rest"): + client = SubscriberClient( + 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.create_snapshot( + pubsub.CreateSnapshotRequest(), + name="name_value", + subscription="subscription_value", + ) + + +def test_create_snapshot_rest_error(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.UpdateSnapshotRequest, + dict, + ], +) +def test_update_snapshot_rest(request_type): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"snapshot": {"name": "projects/sample1/snapshots/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 = pubsub.Snapshot( + name="name_value", + topic="topic_value", + ) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.Snapshot.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.update_snapshot(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pubsub.Snapshot) + assert response.name == "name_value" + assert response.topic == "topic_value" + + +def test_update_snapshot_rest_required_fields( + request_type=pubsub.UpdateSnapshotRequest, +): + transport_class = transports.SubscriberRestTransport + + request_init = {} + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_snapshot._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).update_snapshot._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = pubsub.Snapshot() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "patch", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = pubsub.Snapshot.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.update_snapshot(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_update_snapshot_rest_unset_required_fields(): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.update_snapshot._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "snapshot", + "updateMask", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_update_snapshot_rest_interceptors(null_interceptor): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SubscriberRestInterceptor(), + ) + client = SubscriberClient(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.SubscriberRestInterceptor, "post_update_snapshot" + ) as post, mock.patch.object( + transports.SubscriberRestInterceptor, "pre_update_snapshot" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = pubsub.UpdateSnapshotRequest.pb(pubsub.UpdateSnapshotRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = pubsub.Snapshot.to_json(pubsub.Snapshot()) + + request = pubsub.UpdateSnapshotRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = pubsub.Snapshot() + + client.update_snapshot( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_update_snapshot_rest_bad_request( + transport: str = "rest", request_type=pubsub.UpdateSnapshotRequest +): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"snapshot": {"name": "projects/sample1/snapshots/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.update_snapshot(request) + + +def test_update_snapshot_rest_flattened(): + client = SubscriberClient( + 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 = pubsub.Snapshot() + + # get arguments that satisfy an http rule for this method + sample_request = {"snapshot": {"name": "projects/sample1/snapshots/sample2"}} + + # get truthy value for each flattened field + mock_args = dict( + snapshot=pubsub.Snapshot(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.Snapshot.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.update_snapshot(**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/{snapshot.name=projects/*/snapshots/*}" % client.transport._host, + args[1], + ) + + +def test_update_snapshot_rest_flattened_error(transport: str = "rest"): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_snapshot( + pubsub.UpdateSnapshotRequest(), + snapshot=pubsub.Snapshot(name="name_value"), + update_mask=field_mask_pb2.FieldMask(paths=["paths_value"]), + ) + + +def test_update_snapshot_rest_error(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.DeleteSnapshotRequest, + dict, + ], +) +def test_delete_snapshot_rest(request_type): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"snapshot": "projects/sample1/snapshots/sample2"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.delete_snapshot(request) + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_snapshot_rest_required_fields( + request_type=pubsub.DeleteSnapshotRequest, +): + transport_class = transports.SubscriberRestTransport + + request_init = {} + request_init["snapshot"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_snapshot._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["snapshot"] = "snapshot_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).delete_snapshot._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "snapshot" in jsonified_request + assert jsonified_request["snapshot"] == "snapshot_value" + + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = None + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "delete", + "query_params": pb_request, + } + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.delete_snapshot(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_delete_snapshot_rest_unset_required_fields(): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.delete_snapshot._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("snapshot",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_delete_snapshot_rest_interceptors(null_interceptor): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SubscriberRestInterceptor(), + ) + client = SubscriberClient(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.SubscriberRestInterceptor, "pre_delete_snapshot" + ) as pre: + pre.assert_not_called() + pb_message = pubsub.DeleteSnapshotRequest.pb(pubsub.DeleteSnapshotRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + + request = pubsub.DeleteSnapshotRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + + client.delete_snapshot( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + + +def test_delete_snapshot_rest_bad_request( + transport: str = "rest", request_type=pubsub.DeleteSnapshotRequest +): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"snapshot": "projects/sample1/snapshots/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.delete_snapshot(request) + + +def test_delete_snapshot_rest_flattened(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = None + + # get arguments that satisfy an http rule for this method + sample_request = {"snapshot": "projects/sample1/snapshots/sample2"} + + # get truthy value for each flattened field + mock_args = dict( + snapshot="snapshot_value", + ) + mock_args.update(sample_request) + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = "" + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + client.delete_snapshot(**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/{snapshot=projects/*/snapshots/*}" % client.transport._host, args[1] + ) + + +def test_delete_snapshot_rest_flattened_error(transport: str = "rest"): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_snapshot( + pubsub.DeleteSnapshotRequest(), + snapshot="snapshot_value", + ) + + +def test_delete_snapshot_rest_error(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +@pytest.mark.parametrize( + "request_type", + [ + pubsub.SeekRequest, + dict, + ], +) +def test_seek_rest(request_type): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"subscription": "projects/sample1/subscriptions/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 = pubsub.SeekResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = pubsub.SeekResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.seek(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, pubsub.SeekResponse) + + +def test_seek_rest_required_fields(request_type=pubsub.SeekRequest): + transport_class = transports.SubscriberRestTransport + + request_init = {} + request_init["subscription"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).seek._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["subscription"] = "subscription_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).seek._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "subscription" in jsonified_request + assert jsonified_request["subscription"] == "subscription_value" + + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = pubsub.SeekResponse() + # 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 + + pb_return_value = pubsub.SeekResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.seek(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_seek_rest_unset_required_fields(): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.seek._get_unset_required_fields({}) + assert set(unset_fields) == (set(()) & set(("subscription",))) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_seek_rest_interceptors(null_interceptor): + transport = transports.SubscriberRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.SubscriberRestInterceptor(), + ) + client = SubscriberClient(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.SubscriberRestInterceptor, "post_seek" + ) as post, mock.patch.object( + transports.SubscriberRestInterceptor, "pre_seek" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = pubsub.SeekRequest.pb(pubsub.SeekRequest()) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = pubsub.SeekResponse.to_json(pubsub.SeekResponse()) + + request = pubsub.SeekRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = pubsub.SeekResponse() + + client.seek( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_seek_rest_bad_request( + transport: str = "rest", request_type=pubsub.SeekRequest +): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"subscription": "projects/sample1/subscriptions/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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.seek(request) + + +def test_seek_rest_error(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + +def test_streaming_pull_rest_error(): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + # Since a `google.api.http` annotation is required for using a rest transport + # method, this should error. + with pytest.raises(NotImplementedError) as not_implemented_error: + client.streaming_pull({}) + assert "Method StreamingPull is not available over REST transport" in str( + not_implemented_error.value + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.SubscriberGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.SubscriberGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SubscriberClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide an api_key and a transport instance. + transport = transports.SubscriberGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = SubscriberClient( + client_options=options, + transport=transport, + ) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = SubscriberClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.SubscriberGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SubscriberClient( + client_options={"scopes": ["1", "2"]}, + transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.SubscriberGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + client = SubscriberClient(transport=transport) + assert client.transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.SubscriberGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.SubscriberGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.SubscriberGrpcTransport, + transports.SubscriberGrpcAsyncIOTransport, + transports.SubscriberRestTransport, + ], +) +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() + + +@pytest.mark.parametrize( + "transport_name", + [ + "grpc", + "rest", + ], +) +def test_transport_kind(transport_name): + transport = SubscriberClient.get_transport_class(transport_name)( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert transport.kind == transport_name + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + assert isinstance( + client.transport, + transports.SubscriberGrpcTransport, + ) + + +def test_subscriber_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(core_exceptions.DuplicateCredentialArgs): + transport = transports.SubscriberTransport( + credentials=ga_credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_subscriber_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.pubsub_v1.services.subscriber.transports.SubscriberTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.SubscriberTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "create_subscription", + "get_subscription", + "update_subscription", + "list_subscriptions", + "delete_subscription", + "modify_ack_deadline", + "acknowledge", + "pull", + "streaming_pull", + "modify_push_config", + "get_snapshot", + "list_snapshots", + "create_snapshot", + "update_snapshot", + "delete_snapshot", + "seek", + "set_iam_policy", + "get_iam_policy", + "test_iam_permissions", + ) + 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_subscriber_base_transport_with_credentials_file(): @@ -4991,6 +9160,7 @@ def test_subscriber_transport_auth_adc(transport_class): [ transports.SubscriberGrpcTransport, transports.SubscriberGrpcAsyncIOTransport, + transports.SubscriberRestTransport, ], ) def test_subscriber_transport_auth_gdch_credentials(transport_class): @@ -5092,11 +9262,23 @@ def test_subscriber_grpc_transport_client_cert_source_for_mtls(transport_class): ) +def test_subscriber_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.SubscriberRestTransport( + 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_subscriber_host_no_port(transport_name): @@ -5107,7 +9289,11 @@ def test_subscriber_host_no_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("pubsub.googleapis.com:443") + assert client.transport._host == ( + "pubsub.googleapis.com:443" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://pubsub.googleapis.com" + ) @pytest.mark.parametrize( @@ -5115,6 +9301,7 @@ def test_subscriber_host_no_port(transport_name): [ "grpc", "grpc_asyncio", + "rest", ], ) def test_subscriber_host_with_port(transport_name): @@ -5125,7 +9312,78 @@ def test_subscriber_host_with_port(transport_name): ), transport=transport_name, ) - assert client.transport._host == ("pubsub.googleapis.com:8000") + assert client.transport._host == ( + "pubsub.googleapis.com:8000" + if transport_name in ["grpc", "grpc_asyncio"] + else "https://pubsub.googleapis.com:8000" + ) + + +@pytest.mark.parametrize( + "transport_name", + [ + "rest", + ], +) +def test_subscriber_client_transport_session_collision(transport_name): + creds1 = ga_credentials.AnonymousCredentials() + creds2 = ga_credentials.AnonymousCredentials() + client1 = SubscriberClient( + credentials=creds1, + transport=transport_name, + ) + client2 = SubscriberClient( + credentials=creds2, + transport=transport_name, + ) + session1 = client1.transport.create_subscription._session + session2 = client2.transport.create_subscription._session + assert session1 != session2 + session1 = client1.transport.get_subscription._session + session2 = client2.transport.get_subscription._session + assert session1 != session2 + session1 = client1.transport.update_subscription._session + session2 = client2.transport.update_subscription._session + assert session1 != session2 + session1 = client1.transport.list_subscriptions._session + session2 = client2.transport.list_subscriptions._session + assert session1 != session2 + session1 = client1.transport.delete_subscription._session + session2 = client2.transport.delete_subscription._session + assert session1 != session2 + session1 = client1.transport.modify_ack_deadline._session + session2 = client2.transport.modify_ack_deadline._session + assert session1 != session2 + session1 = client1.transport.acknowledge._session + session2 = client2.transport.acknowledge._session + assert session1 != session2 + session1 = client1.transport.pull._session + session2 = client2.transport.pull._session + assert session1 != session2 + session1 = client1.transport.streaming_pull._session + session2 = client2.transport.streaming_pull._session + assert session1 != session2 + session1 = client1.transport.modify_push_config._session + session2 = client2.transport.modify_push_config._session + assert session1 != session2 + session1 = client1.transport.get_snapshot._session + session2 = client2.transport.get_snapshot._session + assert session1 != session2 + session1 = client1.transport.list_snapshots._session + session2 = client2.transport.list_snapshots._session + assert session1 != session2 + session1 = client1.transport.create_snapshot._session + session2 = client2.transport.create_snapshot._session + assert session1 != session2 + session1 = client1.transport.update_snapshot._session + session2 = client2.transport.update_snapshot._session + assert session1 != session2 + session1 = client1.transport.delete_snapshot._session + session2 = client2.transport.delete_snapshot._session + assert session1 != session2 + session1 = client1.transport.seek._session + session2 = client2.transport.seek._session + assert session1 != session2 def test_subscriber_grpc_transport_channel(): @@ -5459,6 +9717,180 @@ async def test_transport_close_async(): close.assert_called_once() +def test_get_iam_policy_rest_bad_request( + transport: str = "rest", request_type=iam_policy_pb2.GetIamPolicyRequest +): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"resource": "projects/sample1/topics/sample2"}, request + ) + + # 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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.get_iam_policy(request) + + +@pytest.mark.parametrize( + "request_type", + [ + iam_policy_pb2.GetIamPolicyRequest, + dict, + ], +) +def test_get_iam_policy_rest(request_type): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"resource": "projects/sample1/topics/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 = policy_pb2.Policy() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.get_iam_policy(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, policy_pb2.Policy) + + +def test_set_iam_policy_rest_bad_request( + transport: str = "rest", request_type=iam_policy_pb2.SetIamPolicyRequest +): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"resource": "projects/sample1/topics/sample2"}, request + ) + + # 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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.set_iam_policy(request) + + +@pytest.mark.parametrize( + "request_type", + [ + iam_policy_pb2.SetIamPolicyRequest, + dict, + ], +) +def test_set_iam_policy_rest(request_type): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"resource": "projects/sample1/topics/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 = policy_pb2.Policy() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.set_iam_policy(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, policy_pb2.Policy) + + +def test_test_iam_permissions_rest_bad_request( + transport: str = "rest", request_type=iam_policy_pb2.TestIamPermissionsRequest +): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + request = request_type() + request = json_format.ParseDict( + {"resource": "projects/sample1/subscriptions/sample2"}, request + ) + + # 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 = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.test_iam_permissions(request) + + +@pytest.mark.parametrize( + "request_type", + [ + iam_policy_pb2.TestIamPermissionsRequest, + dict, + ], +) +def test_test_iam_permissions_rest(request_type): + client = SubscriberClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request_init = {"resource": "projects/sample1/subscriptions/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 = iam_policy_pb2.TestIamPermissionsResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + json_return_value = json_format.MessageToJson(return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.test_iam_permissions(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, iam_policy_pb2.TestIamPermissionsResponse) + + def test_set_iam_policy(transport: str = "grpc"): client = SubscriberClient( credentials=ga_credentials.AnonymousCredentials(), @@ -5969,6 +10401,7 @@ async def test_test_iam_permissions_from_dict_async(): def test_transport_close(): transports = { + "rest": "_session", "grpc": "_grpc_channel", } @@ -5986,6 +10419,7 @@ def test_transport_close(): def test_client_ctx(): transports = [ + "rest", "grpc", ] for transport in transports: From 0bf8a65f7737539b11010ce99ccceb2c81c67446 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 6 Apr 2023 14:03:53 -0400 Subject: [PATCH 7/7] chore(main): release 2.16.0 (#896) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ google/pubsub/gapic_version.py | 2 +- google/pubsub_v1/gapic_version.py | 2 +- .../snippet_metadata_google.pubsub.v1.json | 2 +- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d83aa2448..2f416052a 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,4 +1,4 @@ { - ".": "2.15.2" + ".": "2.16.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ca26dc37..f4f1f925e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ [1]: https://pypi.org/project/google-cloud-pubsub/#history +## [2.16.0](https://github.com/googleapis/python-pubsub/compare/v2.15.2...v2.16.0) (2023-04-06) + + +### Features + +* Enable "rest" transport in Python for services supporting numeric enums ([#863](https://github.com/googleapis/python-pubsub/issues/863)) ([a80c1d1](https://github.com/googleapis/python-pubsub/commit/a80c1d1f6f880cd13c247231bdc86c824edab8cb)) + + +### Documentation + +* Fix formatting of request arg in docstring ([#894](https://github.com/googleapis/python-pubsub/issues/894)) ([ee2ea73](https://github.com/googleapis/python-pubsub/commit/ee2ea7341268fd5428d98208b8af2fc96efe8d03)) + ## [2.15.2](https://github.com/googleapis/python-pubsub/compare/v2.15.1...v2.15.2) (2023-03-20) diff --git a/google/pubsub/gapic_version.py b/google/pubsub/gapic_version.py index db31fdc2a..a2303530d 100644 --- a/google/pubsub/gapic_version.py +++ b/google/pubsub/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.15.2" # {x-release-please-version} +__version__ = "2.16.0" # {x-release-please-version} diff --git a/google/pubsub_v1/gapic_version.py b/google/pubsub_v1/gapic_version.py index db31fdc2a..a2303530d 100644 --- a/google/pubsub_v1/gapic_version.py +++ b/google/pubsub_v1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.15.2" # {x-release-please-version} +__version__ = "2.16.0" # {x-release-please-version} diff --git a/samples/generated_samples/snippet_metadata_google.pubsub.v1.json b/samples/generated_samples/snippet_metadata_google.pubsub.v1.json index d66015ac4..b80eb543a 100644 --- a/samples/generated_samples/snippet_metadata_google.pubsub.v1.json +++ b/samples/generated_samples/snippet_metadata_google.pubsub.v1.json @@ -8,7 +8,7 @@ ], "language": "PYTHON", "name": "google-cloud-pubsub", - "version": "0.1.0" + "version": "2.16.0" }, "snippets": [ {