From 41dcd30636168f3dd1248f1d99170d531fc9bcb8 Mon Sep 17 00:00:00 2001 From: dpcollins-google <40498610+dpcollins-google@users.noreply.github.com> Date: Mon, 28 Dec 2020 12:03:18 -0500 Subject: [PATCH] fix: Modify synth.py to update grpc transport options (#266) * fix: Modify synth.py to update grpc transport options. * fix: tweak regex replace * chore: regen other docs * chore: make replace required * chore: remove unused import Co-authored-by: Bu Sun Kim --- .flake8 | 1 + .pre-commit-config.yaml | 2 +- CONTRIBUTING.rst | 11 +- google/cloud/pubsub_v1/proto/pubsub.proto | 33 +- google/cloud/pubsub_v1/proto/schema.proto | 289 ++ google/cloud/pubsub_v1/publisher/client.py | 47 +- google/cloud/pubsub_v1/subscriber/client.py | 54 +- google/pubsub/__init__.py | 32 + google/pubsub_v1/__init__.py | 28 + .../services/publisher/async_client.py | 8 +- google/pubsub_v1/services/publisher/client.py | 19 +- .../services/publisher/transports/grpc.py | 8 +- .../publisher/transports/grpc_asyncio.py | 8 +- .../services/schema_service/__init__.py | 24 + .../services/schema_service/async_client.py | 858 ++++++ .../services/schema_service/client.py | 1050 +++++++ .../services/schema_service/pagers.py | 148 + .../schema_service/transports/__init__.py | 35 + .../schema_service/transports/base.py | 227 ++ .../schema_service/transports/grpc.py | 480 +++ .../schema_service/transports/grpc_asyncio.py | 493 ++++ .../services/subscriber/async_client.py | 4 +- .../pubsub_v1/services/subscriber/client.py | 4 +- .../services/subscriber/transports/grpc.py | 6 +- .../subscriber/transports/grpc_asyncio.py | 6 +- google/pubsub_v1/types/__init__.py | 28 + google/pubsub_v1/types/pubsub.py | 34 + google/pubsub_v1/types/schema.py | 249 ++ scripts/fixup_pubsub_v1_keywords.py | 8 +- synth.metadata | 112 +- synth.py | 22 +- tests/unit/gapic/pubsub_v1/test_publisher.py | 102 +- .../gapic/pubsub_v1/test_schema_service.py | 2587 +++++++++++++++++ tests/unit/gapic/pubsub_v1/test_subscriber.py | 2 + .../publisher/test_publisher_client.py | 4 +- .../subscriber/test_subscriber_client.py | 4 +- 36 files changed, 6763 insertions(+), 264 deletions(-) create mode 100644 google/cloud/pubsub_v1/proto/schema.proto create mode 100644 google/pubsub_v1/services/schema_service/__init__.py create mode 100644 google/pubsub_v1/services/schema_service/async_client.py create mode 100644 google/pubsub_v1/services/schema_service/client.py create mode 100644 google/pubsub_v1/services/schema_service/pagers.py create mode 100644 google/pubsub_v1/services/schema_service/transports/__init__.py create mode 100644 google/pubsub_v1/services/schema_service/transports/base.py create mode 100644 google/pubsub_v1/services/schema_service/transports/grpc.py create mode 100644 google/pubsub_v1/services/schema_service/transports/grpc_asyncio.py create mode 100644 google/pubsub_v1/types/schema.py create mode 100644 tests/unit/gapic/pubsub_v1/test_schema_service.py diff --git a/.flake8 b/.flake8 index ed9316381..29227d4cf 100644 --- a/.flake8 +++ b/.flake8 @@ -26,6 +26,7 @@ exclude = *_pb2.py # Standard linting exemptions. + **/.nox/** __pycache__, .git, *.pyc, diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6ad83346e..a9024b15d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.3.0 + rev: v3.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index db4cced2a..290271295 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -21,8 +21,8 @@ In order to add a feature: - The feature must be documented in both the API and narrative documentation. -- The feature must work fully on the following CPython versions: 2.7, - 3.5, 3.6, 3.7 and 3.8 on both UNIX and Windows. +- The feature must work fully on the following CPython versions: + 3.6, 3.7, 3.8 and 3.9 on both UNIX and Windows. - The feature must not add unnecessary dependencies (where "unnecessary" is of course subjective, but new dependencies should @@ -202,25 +202,24 @@ Supported Python Versions We support: -- `Python 3.5`_ - `Python 3.6`_ - `Python 3.7`_ - `Python 3.8`_ +- `Python 3.9`_ -.. _Python 3.5: https://docs.python.org/3.5/ .. _Python 3.6: https://docs.python.org/3.6/ .. _Python 3.7: https://docs.python.org/3.7/ .. _Python 3.8: https://docs.python.org/3.8/ +.. _Python 3.9: https://docs.python.org/3.9/ Supported versions can be found in our ``noxfile.py`` `config`_. .. _config: https://github.com/googleapis/python-pubsub/blob/master/noxfile.py -Python 2.7 support is deprecated. All code changes should maintain Python 2.7 compatibility until January 1, 2020. We also explicitly decided to support Python 3 beginning with version -3.5. Reasons for this include: +3.6. Reasons for this include: - Encouraging use of newest versions of Python 3 - Taking the lead of `prominent`_ open-source `projects`_ diff --git a/google/cloud/pubsub_v1/proto/pubsub.proto b/google/cloud/pubsub_v1/proto/pubsub.proto index 909863eb9..9bc678e3a 100644 --- a/google/cloud/pubsub_v1/proto/pubsub.proto +++ b/google/cloud/pubsub_v1/proto/pubsub.proto @@ -24,6 +24,7 @@ import "google/protobuf/duration.proto"; import "google/protobuf/empty.proto"; import "google/protobuf/field_mask.proto"; import "google/protobuf/timestamp.proto"; +import "google/pubsub/v1/schema.proto"; option cc_enable_arenas = true; option csharp_namespace = "Google.Cloud.PubSub.V1"; @@ -42,8 +43,8 @@ service Publisher { "https://www.googleapis.com/auth/cloud-platform," "https://www.googleapis.com/auth/pubsub"; - // Creates the given topic with the given name. See the [resource name rules]( - // https://cloud.google.com/pubsub/docs/admin#resource_names). + // Creates the given topic with the given name. See the [resource name rules] + // (https://cloud.google.com/pubsub/docs/admin#resource_names). rpc CreateTopic(Topic) returns (Topic) { option (google.api.http) = { put: "/v1/{name=projects/*/topics/*}" @@ -143,6 +144,21 @@ message MessageStoragePolicy { repeated string allowed_persistence_regions = 1; } +// Settings for validating messages published against a schema. +message SchemaSettings { + // Required. The name of the schema that messages published should be + // validated against. Format is `projects/{project}/schemas/{schema}`. The + // value of this field will be `_deleted-schema_` if the schema has been + // deleted. + string schema = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { type: "pubsub.googleapis.com/Schema" } + ]; + + // The encoding of messages validated against `schema`. + Encoding encoding = 2; +} + // A topic resource. message Topic { option (google.api.resource) = { @@ -173,6 +189,15 @@ message Topic { // // The expected format is `projects/*/locations/*/keyRings/*/cryptoKeys/*`. string kms_key_name = 5; + + // Settings for validating messages published against a schema. + // + // EXPERIMENTAL: Schema support is in development and may not work yet. + SchemaSettings schema_settings = 6; + + // Reserved for future use. This field is set only in responses from the + // server; it is ignored if it is set in any requests. + bool satisfies_pzs = 7; } // A message that is published by publishers and consumed by subscribers. The @@ -587,8 +612,8 @@ service Subscriber { } // Seeks an existing subscription to a point in time or to a given snapshot, - // whichever is provided in the request. Snapshots are used in [Seek]( - // https://cloud.google.com/pubsub/docs/replay-overview) operations, which + // whichever is provided in the request. Snapshots are used in [Seek] + // (https://cloud.google.com/pubsub/docs/replay-overview) 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. Note that both the subscription and the diff --git a/google/cloud/pubsub_v1/proto/schema.proto b/google/cloud/pubsub_v1/proto/schema.proto new file mode 100644 index 000000000..ae402ac4d --- /dev/null +++ b/google/cloud/pubsub_v1/proto/schema.proto @@ -0,0 +1,289 @@ +// Copyright 2020 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. + +syntax = "proto3"; + +package google.pubsub.v1; + +import "google/api/annotations.proto"; +import "google/api/client.proto"; +import "google/api/field_behavior.proto"; +import "google/api/resource.proto"; +import "google/protobuf/empty.proto"; + +option cc_enable_arenas = true; +option csharp_namespace = "Google.Cloud.PubSub.V1"; +option go_package = "google.golang.org/genproto/googleapis/pubsub/v1;pubsub"; +option java_multiple_files = true; +option java_outer_classname = "SchemaProto"; +option java_package = "com.google.pubsub.v1"; +option php_namespace = "Google\\Cloud\\PubSub\\V1"; +option ruby_package = "Google::Cloud::PubSub::V1"; + +// Service for doing schema-related operations. +// +// EXPERIMENTAL: The Schema service is in development and may not work yet. + +service SchemaService { + option (google.api.default_host) = "pubsub.googleapis.com"; + option (google.api.oauth_scopes) = + "https://www.googleapis.com/auth/cloud-platform," + "https://www.googleapis.com/auth/pubsub"; + + // Creates a schema. + rpc CreateSchema(CreateSchemaRequest) returns (Schema) { + option (google.api.http) = { + post: "/v1/{parent=projects/*}/schemas" + body: "schema" + }; + option (google.api.method_signature) = "parent,schema,schema_id"; + } + + // Gets a schema. + rpc GetSchema(GetSchemaRequest) returns (Schema) { + option (google.api.http) = { + get: "/v1/{name=projects/*/schemas/*}" + }; + option (google.api.method_signature) = "name"; + } + + // Lists schemas in a project. + rpc ListSchemas(ListSchemasRequest) returns (ListSchemasResponse) { + option (google.api.http) = { + get: "/v1/{parent=projects/*}/schemas" + }; + option (google.api.method_signature) = "parent"; + } + + // Deletes a schema. + rpc DeleteSchema(DeleteSchemaRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/v1/{name=projects/*/schemas/*}" + }; + option (google.api.method_signature) = "name"; + } + + // Validates a schema. + rpc ValidateSchema(ValidateSchemaRequest) returns (ValidateSchemaResponse) { + option (google.api.http) = { + post: "/v1/{parent=projects/*}/schemas:validate" + body: "*" + }; + option (google.api.method_signature) = "parent,schema"; + } + + // Validates a message against a schema. + rpc ValidateMessage(ValidateMessageRequest) + returns (ValidateMessageResponse) { + option (google.api.http) = { + post: "/v1/{parent=projects/*}/schemas:validateMessage" + body: "*" + }; + } +} + +// A schema resource. +message Schema { + option (google.api.resource) = { + type: "pubsub.googleapis.com/Schema" + pattern: "projects/{project}/schemas/{schema}" + }; + + // Possible schema definition types. + enum Type { + // Default value. This value is unused. + TYPE_UNSPECIFIED = 0; + + // A Protocol Buffer schema definition. + PROTOCOL_BUFFER = 1; + + // An Avro schema definition. + AVRO = 2; + } + + // Required. Name of the schema. + // Format is `projects/{project}/schemas/{schema}`. + string name = 1 [(google.api.field_behavior) = REQUIRED]; + + // The type of the schema definition. + Type type = 2; + + // The definition of the schema. This should contain a string representing + // the full definition of the schema that is a valid schema definition of + // the type specified in `type`. + string definition = 3; +} + +// Request for the CreateSchema method. +message CreateSchemaRequest { + // Required. The name of the project in which to create the schema. + // Format is `projects/{project-id}`. + string parent = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + child_type: "pubsub.googleapis.com/Schema" + } + ]; + + // Required. The schema object to create. + // + // This schema's `name` parameter is ignored. The schema object returned + // by CreateSchema will have a `name` made using the given `parent` and + // `schema_id`. + Schema schema = 2 [(google.api.field_behavior) = REQUIRED]; + + // The ID to use for the schema, which will become the final component of + // the schema's resource name. + // + // See https://cloud.google.com/pubsub/docs/admin#resource_names for resource + // name constraints. + string schema_id = 3; +} + +// View of Schema object fields to be returned by GetSchema and ListSchemas. +enum SchemaView { + // The default / unset value. + // The API will default to the BASIC view. + SCHEMA_VIEW_UNSPECIFIED = 0; + + // Include the name and type of the schema, but not the definition. + BASIC = 1; + + // Include all Schema object fields. + FULL = 2; +} + +// Request for the GetSchema method. +message GetSchemaRequest { + // Required. The name of the schema to get. + // Format is `projects/{project}/schemas/{schema}`. + string name = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { type: "pubsub.googleapis.com/Schema" } + ]; + + // The set of fields to return in the response. If not set, returns a Schema + // with `name` and `type`, but not `definition`. Set to `FULL` to retrieve all + // fields. + SchemaView view = 2; +} + +// Request for the `ListSchemas` method. +message ListSchemasRequest { + // Required. The name of the project in which to list schemas. + // Format is `projects/{project-id}`. + string parent = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + type: "cloudresourcemanager.googleapis.com/Project" + } + ]; + + // The set of Schema fields to return in the response. If not set, returns + // Schemas with `name` and `type`, but not `definition`. Set to `FULL` to + // retrieve all fields. + SchemaView view = 2; + + // Maximum number of schemas to return. + int32 page_size = 3; + + // The value returned by the last `ListSchemasResponse`; indicates that + // this is a continuation of a prior `ListSchemas` call, and that the + // system should return the next page of data. + string page_token = 4; +} + +// Response for the `ListSchemas` method. +message ListSchemasResponse { + // The resulting schemas. + repeated Schema schemas = 1; + + // If not empty, indicates that there may be more schemas that match the + // request; this value should be passed in a new `ListSchemasRequest`. + string next_page_token = 2; +} + +// Request for the `DeleteSchema` method. +message DeleteSchemaRequest { + // Required. Name of the schema to delete. + // Format is `projects/{project}/schemas/{schema}`. + string name = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { type: "pubsub.googleapis.com/Schema" } + ]; +} + +// Request for the `ValidateSchema` method. +message ValidateSchemaRequest { + // Required. The name of the project in which to validate schemas. + // Format is `projects/{project-id}`. + string parent = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + type: "cloudresourcemanager.googleapis.com/Project" + } + ]; + + // Required. The schema object to validate. + Schema schema = 2 [(google.api.field_behavior) = REQUIRED]; +} + +// Response for the `ValidateSchema` method. +message ValidateSchemaResponse {} + +// Request for the `ValidateMessage` method. +message ValidateMessageRequest { + // Required. The name of the project in which to validate schemas. + // Format is `projects/{project-id}`. + string parent = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + type: "cloudresourcemanager.googleapis.com/Project" + } + ]; + + oneof schema_spec { + // Name of the schema against which to validate. + // + // Format is `projects/{project}/schemas/{schema}`. + string name = 2 [ + (google.api.resource_reference) = { type: "pubsub.googleapis.com/Schema" } + ]; + + // Ad-hoc schema against which to validate + Schema schema = 3; + } + + // Message to validate against the provided `schema_spec`. + bytes message = 4; + + // The encoding expected for messages + Encoding encoding = 5; +} + +// Response for the `ValidateMessage` method. +message ValidateMessageResponse {} + +// Possible encoding types for messages. +enum Encoding { + // Unspecified + ENCODING_UNSPECIFIED = 0; + + // JSON encoding + JSON = 1; + + // Binary encoding, as defined by the schema type. For some schema types, + // binary encoding may not be available. + BINARY = 2; +} diff --git a/google/cloud/pubsub_v1/publisher/client.py b/google/cloud/pubsub_v1/publisher/client.py index 81cfdea10..9ad4a9474 100644 --- a/google/cloud/pubsub_v1/publisher/client.py +++ b/google/cloud/pubsub_v1/publisher/client.py @@ -21,11 +21,9 @@ import threading import time -import grpc import six from google.api_core import gapic_v1 -from google.api_core import grpc_helpers from google.auth.credentials import AnonymousCredentials from google.oauth2 import service_account @@ -39,9 +37,6 @@ from google.cloud.pubsub_v1.publisher.flow_controller import FlowController from google.pubsub_v1 import types as gapic_types from google.pubsub_v1.services.publisher import client as publisher_client -from google.pubsub_v1.services.publisher.transports import ( - grpc as publisher_grpc_transport, -) __version__ = pkg_resources.get_distribution("google-cloud-pubsub").version @@ -127,46 +122,11 @@ def __init__(self, batch_settings=(), publisher_options=(), **kwargs): # If so, create a grpc insecure channel with the emulator host # as the target. if os.environ.get("PUBSUB_EMULATOR_HOST"): - kwargs["channel"] = grpc.insecure_channel( - target=os.environ.get("PUBSUB_EMULATOR_HOST") - ) + kwargs["client_options"] = { + "api_endpoint": os.environ.get("PUBSUB_EMULATOR_HOST") + } kwargs["credentials"] = AnonymousCredentials() - # The GAPIC client has mTLS logic to determine the api endpoint and the - # ssl credentials to use. Here we create a GAPIC client to help compute the - # api endpoint and ssl credentials. The api endpoint will be used to set - # `self._target`, and ssl credentials will be passed to - # `grpc_helpers.create_channel` to establish a mTLS channel (if ssl - # credentials is not None). - client_options = kwargs.get("client_options", None) - credentials = kwargs.get("credentials", None) - client_for_mtls_info = publisher_client.PublisherClient( - credentials=credentials, client_options=client_options - ) - - self._target = client_for_mtls_info._transport._host - - # Use a custom channel. - # We need this in order to set appropriate default message size and - # keepalive options. - if "transport" not in kwargs: - channel = kwargs.pop("channel", None) - if channel is None: - channel = grpc_helpers.create_channel( - credentials=kwargs.pop("credentials", None), - target=self.target, - ssl_credentials=client_for_mtls_info._transport._ssl_channel_credentials, - scopes=publisher_client.PublisherClient._DEFAULT_SCOPES, - options={ - "grpc.max_send_message_length": -1, - "grpc.max_receive_message_length": -1, - }.items(), - ) - # cannot pass both 'channel' and 'credentials' - kwargs.pop("credentials", None) - transport = publisher_grpc_transport.PublisherGrpcTransport(channel=channel) - kwargs["transport"] = transport - # For a transient failure, retry publishing the message infinitely. self.publisher_options = types.PublisherOptions(*publisher_options) self._enable_message_ordering = self.publisher_options[0] @@ -174,6 +134,7 @@ def __init__(self, batch_settings=(), publisher_options=(), **kwargs): # Add the metrics headers, and instantiate the underlying GAPIC # client. self.api = publisher_client.PublisherClient(**kwargs) + self._target = self.api._transport._host self._batch_class = thread.Batch self.batch_settings = types.BatchSettings(*batch_settings) diff --git a/google/cloud/pubsub_v1/subscriber/client.py b/google/cloud/pubsub_v1/subscriber/client.py index a1f93e52e..f306d2d99 100644 --- a/google/cloud/pubsub_v1/subscriber/client.py +++ b/google/cloud/pubsub_v1/subscriber/client.py @@ -17,9 +17,6 @@ import os import pkg_resources -import grpc - -from google.api_core import grpc_helpers from google.auth.credentials import AnonymousCredentials from google.oauth2 import service_account @@ -28,9 +25,6 @@ from google.cloud.pubsub_v1.subscriber import futures from google.cloud.pubsub_v1.subscriber._protocol import streaming_pull_manager from google.pubsub_v1.services.subscriber import client as subscriber_client -from google.pubsub_v1.services.subscriber.transports import ( - grpc as subscriber_grpc_transport, -) __version__ = pkg_resources.get_distribution("google-cloud-pubsub").version @@ -78,52 +72,14 @@ def __init__(self, **kwargs): # If so, create a grpc insecure channel with the emulator host # as the target. if os.environ.get("PUBSUB_EMULATOR_HOST"): - kwargs["channel"] = grpc.insecure_channel( - target=os.environ.get("PUBSUB_EMULATOR_HOST") - ) + kwargs["client_options"] = { + "api_endpoint": os.environ.get("PUBSUB_EMULATOR_HOST") + } kwargs["credentials"] = AnonymousCredentials() - # The GAPIC client has mTLS logic to determine the api endpoint and the - # ssl credentials to use. Here we create a GAPIC client to help compute the - # api endpoint and ssl credentials. The api endpoint will be used to set - # `self._target`, and ssl credentials will be passed to - # `grpc_helpers.create_channel` to establish a mTLS channel (if ssl - # credentials is not None). - client_options = kwargs.get("client_options", None) - credentials = kwargs.get("credentials", None) - client_for_mtls_info = subscriber_client.SubscriberClient( - credentials=credentials, client_options=client_options - ) - - self._target = client_for_mtls_info._transport._host - - # Use a custom channel. - # We need this in order to set appropriate default message size and - # keepalive options. - if "transport" not in kwargs: - channel = kwargs.pop("channel", None) - if channel is None: - channel = grpc_helpers.create_channel( - credentials=kwargs.pop("credentials", None), - target=self.target, - ssl_credentials=client_for_mtls_info._transport._ssl_channel_credentials, - scopes=subscriber_client.SubscriberClient._DEFAULT_SCOPES, - options={ - "grpc.max_send_message_length": -1, - "grpc.max_receive_message_length": -1, - "grpc.keepalive_time_ms": 30000, - }.items(), - ) - # cannot pass both 'channel' and 'credentials' - kwargs.pop("credentials", None) - transport = subscriber_grpc_transport.SubscriberGrpcTransport( - channel=channel - ) - kwargs["transport"] = transport - - # Add the metrics headers, and instantiate the underlying GAPIC - # client. + # Instantiate the underlying GAPIC client. self._api = subscriber_client.SubscriberClient(**kwargs) + self._target = self._api._transport._host @classmethod def from_service_account_file(cls, filename, **kwargs): diff --git a/google/pubsub/__init__.py b/google/pubsub/__init__.py index f441543cc..b146fadef 100644 --- a/google/pubsub/__init__.py +++ b/google/pubsub/__init__.py @@ -17,6 +17,10 @@ from google.pubsub_v1.services.publisher.async_client import PublisherAsyncClient from google.pubsub_v1.services.publisher.client import PublisherClient +from google.pubsub_v1.services.schema_service.async_client import ( + SchemaServiceAsyncClient, +) +from google.pubsub_v1.services.schema_service.client import SchemaServiceClient from google.pubsub_v1.services.subscriber.async_client import SubscriberAsyncClient from google.pubsub_v1.services.subscriber.client import SubscriberClient from google.pubsub_v1.types.pubsub import AcknowledgeRequest @@ -52,6 +56,7 @@ from google.pubsub_v1.types.pubsub import PushConfig from google.pubsub_v1.types.pubsub import ReceivedMessage from google.pubsub_v1.types.pubsub import RetryPolicy +from google.pubsub_v1.types.pubsub import SchemaSettings from google.pubsub_v1.types.pubsub import SeekRequest from google.pubsub_v1.types.pubsub import SeekResponse from google.pubsub_v1.types.pubsub import Snapshot @@ -62,20 +67,38 @@ from google.pubsub_v1.types.pubsub import UpdateSnapshotRequest from google.pubsub_v1.types.pubsub import UpdateSubscriptionRequest from google.pubsub_v1.types.pubsub import UpdateTopicRequest +from google.pubsub_v1.types.schema import CreateSchemaRequest +from google.pubsub_v1.types.schema import DeleteSchemaRequest +from google.pubsub_v1.types.schema import Encoding +from google.pubsub_v1.types.schema import GetSchemaRequest +from google.pubsub_v1.types.schema import ListSchemasRequest +from google.pubsub_v1.types.schema import ListSchemasResponse +from google.pubsub_v1.types.schema import Schema +from google.pubsub_v1.types.schema import SchemaView +from google.pubsub_v1.types.schema import ValidateMessageRequest +from google.pubsub_v1.types.schema import ValidateMessageResponse +from google.pubsub_v1.types.schema import ValidateSchemaRequest +from google.pubsub_v1.types.schema import ValidateSchemaResponse __all__ = ( "AcknowledgeRequest", + "CreateSchemaRequest", "CreateSnapshotRequest", "DeadLetterPolicy", + "DeleteSchemaRequest", "DeleteSnapshotRequest", "DeleteSubscriptionRequest", "DeleteTopicRequest", "DetachSubscriptionRequest", "DetachSubscriptionResponse", + "Encoding", "ExpirationPolicy", + "GetSchemaRequest", "GetSnapshotRequest", "GetSubscriptionRequest", "GetTopicRequest", + "ListSchemasRequest", + "ListSchemasResponse", "ListSnapshotsRequest", "ListSnapshotsResponse", "ListSubscriptionsRequest", @@ -99,6 +122,11 @@ "PushConfig", "ReceivedMessage", "RetryPolicy", + "Schema", + "SchemaServiceAsyncClient", + "SchemaServiceClient", + "SchemaSettings", + "SchemaView", "SeekRequest", "SeekResponse", "Snapshot", @@ -111,4 +139,8 @@ "UpdateSnapshotRequest", "UpdateSubscriptionRequest", "UpdateTopicRequest", + "ValidateMessageRequest", + "ValidateMessageResponse", + "ValidateSchemaRequest", + "ValidateSchemaResponse", ) diff --git a/google/pubsub_v1/__init__.py b/google/pubsub_v1/__init__.py index 5e7a6cc72..d5a1de488 100644 --- a/google/pubsub_v1/__init__.py +++ b/google/pubsub_v1/__init__.py @@ -16,6 +16,7 @@ # from .services.publisher import PublisherClient +from .services.schema_service import SchemaServiceClient from .services.subscriber import SubscriberClient from .types.pubsub import AcknowledgeRequest from .types.pubsub import CreateSnapshotRequest @@ -50,6 +51,7 @@ from .types.pubsub import PushConfig from .types.pubsub import ReceivedMessage from .types.pubsub import RetryPolicy +from .types.pubsub import SchemaSettings from .types.pubsub import SeekRequest from .types.pubsub import SeekResponse from .types.pubsub import Snapshot @@ -60,21 +62,39 @@ from .types.pubsub import UpdateSnapshotRequest from .types.pubsub import UpdateSubscriptionRequest from .types.pubsub import UpdateTopicRequest +from .types.schema import CreateSchemaRequest +from .types.schema import DeleteSchemaRequest +from .types.schema import Encoding +from .types.schema import GetSchemaRequest +from .types.schema import ListSchemasRequest +from .types.schema import ListSchemasResponse +from .types.schema import Schema +from .types.schema import SchemaView +from .types.schema import ValidateMessageRequest +from .types.schema import ValidateMessageResponse +from .types.schema import ValidateSchemaRequest +from .types.schema import ValidateSchemaResponse __all__ = ( "AcknowledgeRequest", + "CreateSchemaRequest", "CreateSnapshotRequest", "DeadLetterPolicy", + "DeleteSchemaRequest", "DeleteSnapshotRequest", "DeleteSubscriptionRequest", "DeleteTopicRequest", "DetachSubscriptionRequest", "DetachSubscriptionResponse", + "Encoding", "ExpirationPolicy", + "GetSchemaRequest", "GetSnapshotRequest", "GetSubscriptionRequest", "GetTopicRequest", + "ListSchemasRequest", + "ListSchemasResponse", "ListSnapshotsRequest", "ListSnapshotsResponse", "ListSubscriptionsRequest", @@ -96,6 +116,10 @@ "PushConfig", "ReceivedMessage", "RetryPolicy", + "Schema", + "SchemaServiceClient", + "SchemaSettings", + "SchemaView", "SeekRequest", "SeekResponse", "Snapshot", @@ -107,5 +131,9 @@ "UpdateSnapshotRequest", "UpdateSubscriptionRequest", "UpdateTopicRequest", + "ValidateMessageRequest", + "ValidateMessageResponse", + "ValidateSchemaRequest", + "ValidateSchemaResponse", "PublisherClient", ) diff --git a/google/pubsub_v1/services/publisher/async_client.py b/google/pubsub_v1/services/publisher/async_client.py index fdf3aeb37..810bf532e 100644 --- a/google/pubsub_v1/services/publisher/async_client.py +++ b/google/pubsub_v1/services/publisher/async_client.py @@ -48,6 +48,8 @@ class PublisherAsyncClient: DEFAULT_ENDPOINT = PublisherClient.DEFAULT_ENDPOINT DEFAULT_MTLS_ENDPOINT = PublisherClient.DEFAULT_MTLS_ENDPOINT + schema_path = staticmethod(PublisherClient.schema_path) + parse_schema_path = staticmethod(PublisherClient.parse_schema_path) subscription_path = staticmethod(PublisherClient.subscription_path) parse_subscription_path = staticmethod(PublisherClient.parse_subscription_path) topic_path = staticmethod(PublisherClient.topic_path) @@ -149,9 +151,9 @@ async def create_topic( timeout: float = None, metadata: Sequence[Tuple[str, str]] = (), ) -> pubsub.Topic: - r"""Creates the given topic with the given name. See the `resource - name - rules `__. + r"""Creates the given topic with the given name. See the [resource + name rules] + (https://cloud.google.com/pubsub/docs/admin#resource_names). Args: request (:class:`~.pubsub.Topic`): diff --git a/google/pubsub_v1/services/publisher/client.py b/google/pubsub_v1/services/publisher/client.py index 188b3dccb..d2c8f853f 100644 --- a/google/pubsub_v1/services/publisher/client.py +++ b/google/pubsub_v1/services/publisher/client.py @@ -154,6 +154,19 @@ def transport(self) -> PublisherTransport: """ return self._transport + @staticmethod + def schema_path(project: str, schema: str,) -> str: + """Return a fully-qualified schema string.""" + return "projects/{project}/schemas/{schema}".format( + project=project, schema=schema, + ) + + @staticmethod + def parse_schema_path(path: str) -> Dict[str, str]: + """Parse a schema path into its component segments.""" + m = re.match(r"^projects/(?P.+?)/schemas/(?P.+?)$", path) + return m.groupdict() if m else {} + @staticmethod def subscription_path(project: str, subscription: str,) -> str: """Return a fully-qualified subscription string.""" @@ -366,9 +379,9 @@ def create_topic( timeout: float = None, metadata: Sequence[Tuple[str, str]] = (), ) -> pubsub.Topic: - r"""Creates the given topic with the given name. See the `resource - name - rules `__. + r"""Creates the given topic with the given name. See the [resource + name rules] + (https://cloud.google.com/pubsub/docs/admin#resource_names). Args: diff --git a/google/pubsub_v1/services/publisher/transports/grpc.py b/google/pubsub_v1/services/publisher/transports/grpc.py index 3661714b2..d1212ecea 100644 --- a/google/pubsub_v1/services/publisher/transports/grpc.py +++ b/google/pubsub_v1/services/publisher/transports/grpc.py @@ -153,6 +153,7 @@ def __init__( options=[ ("grpc.max_send_message_length", -1), ("grpc.max_receive_message_length", -1), + ("grpc.keepalive_time_ms", 30000), ], ) self._ssl_channel_credentials = ssl_credentials @@ -175,6 +176,7 @@ def __init__( options=[ ("grpc.max_send_message_length", -1), ("grpc.max_receive_message_length", -1), + ("grpc.keepalive_time_ms", 30000), ], ) @@ -245,9 +247,9 @@ def grpc_channel(self) -> grpc.Channel: def create_topic(self) -> Callable[[pubsub.Topic], pubsub.Topic]: r"""Return a callable for the create topic method over gRPC. - Creates the given topic with the given name. See the `resource - name - rules `__. + Creates the given topic with the given name. See the [resource + name rules] + (https://cloud.google.com/pubsub/docs/admin#resource_names). Returns: Callable[[~.Topic], diff --git a/google/pubsub_v1/services/publisher/transports/grpc_asyncio.py b/google/pubsub_v1/services/publisher/transports/grpc_asyncio.py index c8945f8e3..2b15178ef 100644 --- a/google/pubsub_v1/services/publisher/transports/grpc_asyncio.py +++ b/google/pubsub_v1/services/publisher/transports/grpc_asyncio.py @@ -198,6 +198,7 @@ def __init__( options=[ ("grpc.max_send_message_length", -1), ("grpc.max_receive_message_length", -1), + ("grpc.keepalive_time_ms", 30000), ], ) self._ssl_channel_credentials = ssl_credentials @@ -220,6 +221,7 @@ def __init__( options=[ ("grpc.max_send_message_length", -1), ("grpc.max_receive_message_length", -1), + ("grpc.keepalive_time_ms", 30000), ], ) @@ -249,9 +251,9 @@ def grpc_channel(self) -> aio.Channel: def create_topic(self) -> Callable[[pubsub.Topic], Awaitable[pubsub.Topic]]: r"""Return a callable for the create topic method over gRPC. - Creates the given topic with the given name. See the `resource - name - rules `__. + Creates the given topic with the given name. See the [resource + name rules] + (https://cloud.google.com/pubsub/docs/admin#resource_names). Returns: Callable[[~.Topic], diff --git a/google/pubsub_v1/services/schema_service/__init__.py b/google/pubsub_v1/services/schema_service/__init__.py new file mode 100644 index 000000000..d93cff56f --- /dev/null +++ b/google/pubsub_v1/services/schema_service/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 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 .client import SchemaServiceClient +from .async_client import SchemaServiceAsyncClient + +__all__ = ( + "SchemaServiceClient", + "SchemaServiceAsyncClient", +) diff --git a/google/pubsub_v1/services/schema_service/async_client.py b/google/pubsub_v1/services/schema_service/async_client.py new file mode 100644 index 000000000..4a6d1db93 --- /dev/null +++ b/google/pubsub_v1/services/schema_service/async_client.py @@ -0,0 +1,858 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +import functools +import re +from typing import Dict, Sequence, Tuple, Type, Union +import pkg_resources + +import google.api_core.client_options as ClientOptions # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.auth import credentials # type: ignore +from google.oauth2 import service_account # type: ignore + +from google.iam.v1 import iam_policy_pb2 as iam_policy # type: ignore +from google.iam.v1 import policy_pb2 as policy # type: ignore +from google.pubsub_v1.services.schema_service import pagers +from google.pubsub_v1.types import schema +from google.pubsub_v1.types import schema as gp_schema + +from .transports.base import SchemaServiceTransport, DEFAULT_CLIENT_INFO +from .transports.grpc_asyncio import SchemaServiceGrpcAsyncIOTransport +from .client import SchemaServiceClient + + +class SchemaServiceAsyncClient: + """ Service for doing schema-related operations. + EXPERIMENTAL: The Schema service is in development and may not + work yet. + """ + + _client: SchemaServiceClient + + DEFAULT_ENDPOINT = SchemaServiceClient.DEFAULT_ENDPOINT + DEFAULT_MTLS_ENDPOINT = SchemaServiceClient.DEFAULT_MTLS_ENDPOINT + + schema_path = staticmethod(SchemaServiceClient.schema_path) + parse_schema_path = staticmethod(SchemaServiceClient.parse_schema_path) + + common_billing_account_path = staticmethod( + SchemaServiceClient.common_billing_account_path + ) + parse_common_billing_account_path = staticmethod( + SchemaServiceClient.parse_common_billing_account_path + ) + + common_folder_path = staticmethod(SchemaServiceClient.common_folder_path) + parse_common_folder_path = staticmethod( + SchemaServiceClient.parse_common_folder_path + ) + + common_organization_path = staticmethod( + SchemaServiceClient.common_organization_path + ) + parse_common_organization_path = staticmethod( + SchemaServiceClient.parse_common_organization_path + ) + + common_project_path = staticmethod(SchemaServiceClient.common_project_path) + parse_common_project_path = staticmethod( + SchemaServiceClient.parse_common_project_path + ) + + common_location_path = staticmethod(SchemaServiceClient.common_location_path) + parse_common_location_path = staticmethod( + SchemaServiceClient.parse_common_location_path + ) + + from_service_account_file = SchemaServiceClient.from_service_account_file + from_service_account_json = from_service_account_file + + @property + def transport(self) -> SchemaServiceTransport: + """Return the transport used by the client instance. + + Returns: + SchemaServiceTransport: The transport used by the client instance. + """ + return self._client.transport + + get_transport_class = functools.partial( + type(SchemaServiceClient).get_transport_class, type(SchemaServiceClient) + ) + + def __init__( + self, + *, + credentials: credentials.Credentials = None, + transport: Union[str, SchemaServiceTransport] = "grpc_asyncio", + client_options: ClientOptions = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the schema service client. + + Args: + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + transport (Union[str, ~.SchemaServiceTransport]): The + transport to use. If set to None, a transport is chosen + automatically. + client_options (ClientOptions): Custom options for the client. It + won't take effect if a ``transport`` instance is provided. + (1) The ``api_endpoint`` property can be used to override the + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT + environment variable can also be used to override the endpoint: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + """ + + self._client = SchemaServiceClient( + credentials=credentials, + transport=transport, + client_options=client_options, + client_info=client_info, + ) + + async def create_schema( + self, + request: gp_schema.CreateSchemaRequest = None, + *, + parent: str = None, + schema: gp_schema.Schema = None, + schema_id: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gp_schema.Schema: + r"""Creates a schema. + + Args: + request (:class:`~.gp_schema.CreateSchemaRequest`): + The request object. Request for the CreateSchema method. + parent (:class:`str`): + Required. The name of the project in which to create the + schema. Format is ``projects/{project-id}``. + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + schema (:class:`~.gp_schema.Schema`): + Required. The schema object to create. + + This schema's ``name`` parameter is ignored. The schema + object returned by CreateSchema will have a ``name`` + made using the given ``parent`` and ``schema_id``. + This corresponds to the ``schema`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + schema_id (:class:`str`): + The ID to use for the schema, which will become the + final component of the schema's resource name. + + See + https://cloud.google.com/pubsub/docs/admin#resource_names + for resource name constraints. + This corresponds to the ``schema_id`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.gp_schema.Schema: + A schema resource. + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([parent, schema, schema_id]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = gp_schema.CreateSchemaRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if schema is not None: + request.schema = schema + if schema_id is not None: + request.schema_id = schema_id + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.create_schema, + default_timeout=None, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + async def get_schema( + self, + request: schema.GetSchemaRequest = None, + *, + name: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> schema.Schema: + r"""Gets a schema. + + Args: + request (:class:`~.schema.GetSchemaRequest`): + The request object. Request for the GetSchema method. + name (:class:`str`): + Required. The name of the schema to get. Format is + ``projects/{project}/schemas/{schema}``. + This corresponds to the ``name`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.schema.Schema: + A schema resource. + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([name]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = schema.GetSchemaRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.get_schema, + default_timeout=None, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + async def list_schemas( + self, + request: schema.ListSchemasRequest = None, + *, + parent: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pagers.ListSchemasAsyncPager: + r"""Lists schemas in a project. + + Args: + request (:class:`~.schema.ListSchemasRequest`): + 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}``. + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pagers.ListSchemasAsyncPager: + Response for the ``ListSchemas`` method. + + Iterating over this object will yield results and + resolve additional pages automatically. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([parent]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = schema.ListSchemasRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.list_schemas, + default_timeout=None, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # This method is paged; wrap the response in a pager, which provides + # an `__aiter__` convenience method. + response = pagers.ListSchemasAsyncPager( + method=rpc, request=request, response=response, metadata=metadata, + ) + + # Done; return the response. + return response + + async def delete_schema( + self, + request: schema.DeleteSchemaRequest = None, + *, + name: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + r"""Deletes a schema. + + Args: + request (:class:`~.schema.DeleteSchemaRequest`): + The request object. Request for the `DeleteSchema` + method. + name (:class:`str`): + Required. Name of the schema to delete. Format is + ``projects/{project}/schemas/{schema}``. + This corresponds to the ``name`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([name]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = schema.DeleteSchemaRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.delete_schema, + default_timeout=None, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Send the request. + await rpc( + request, retry=retry, timeout=timeout, metadata=metadata, + ) + + async def validate_schema( + self, + request: gp_schema.ValidateSchemaRequest = None, + *, + parent: str = None, + schema: gp_schema.Schema = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gp_schema.ValidateSchemaResponse: + r"""Validates a schema. + + Args: + request (:class:`~.gp_schema.ValidateSchemaRequest`): + 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}``. + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + schema (:class:`~.gp_schema.Schema`): + Required. The schema object to + validate. + This corresponds to the ``schema`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.gp_schema.ValidateSchemaResponse: + Response for the ``ValidateSchema`` method. + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([parent, schema]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = gp_schema.ValidateSchemaRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if schema is not None: + request.schema = schema + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.validate_schema, + default_timeout=None, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + async def validate_message( + self, + request: schema.ValidateMessageRequest = None, + *, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> schema.ValidateMessageResponse: + r"""Validates a message against a schema. + + Args: + request (:class:`~.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. + """ + # Create or coerce a protobuf request object. + + request = schema.ValidateMessageRequest(request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.validate_message, + default_timeout=None, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + async def set_iam_policy( + self, + request: iam_policy.SetIamPolicyRequest = None, + *, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> policy.Policy: + r"""Sets the IAM access control policy on the specified + function. Replaces any existing policy. + Args: + request (:class:`~.iam_policy.SetIamPolicyRequest`): + The request object. Request message 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.Policy: + Defines an Identity and Access Management (IAM) policy. + It is used to specify access control policies for Cloud + Platform resources. + A ``Policy`` is a collection of ``bindings``. A + ``binding`` binds one or more ``members`` to a single + ``role``. Members can be user accounts, service + accounts, Google groups, and domains (such as G Suite). + A ``role`` is a named list of permissions (defined by + IAM or configured by users). A ``binding`` can + optionally specify a ``condition``, which is a logic + expression that further constrains the role binding + based on attributes about the request and/or target + resource. + **JSON Example** + :: + { + "bindings": [ + { + "role": "roles/resourcemanager.organizationAdmin", + "members": [ + "user:mike@example.com", + "group:admins@example.com", + "domain:google.com", + "serviceAccount:my-project-id@appspot.gserviceaccount.com" + ] + }, + { + "role": "roles/resourcemanager.organizationViewer", + "members": ["user:eve@example.com"], + "condition": { + "title": "expirable access", + "description": "Does not grant access after Sep 2020", + "expression": "request.time < + timestamp('2020-10-01T00:00:00.000Z')", + } + } + ] + } + **YAML Example** + :: + bindings: + - members: + - user:mike@example.com + - group:admins@example.com + - domain:google.com + - serviceAccount:my-project-id@appspot.gserviceaccount.com + role: roles/resourcemanager.organizationAdmin + - members: + - user:eve@example.com + role: roles/resourcemanager.organizationViewer + condition: + title: expirable access + description: Does not grant access after Sep 2020 + expression: request.time < timestamp('2020-10-01T00:00:00.000Z') + For a description of IAM and its features, see the `IAM + developer's + guide `__. + """ + # Create or coerce a protobuf request object. + + # The request isn't a proto-plus wrapped type, + # so it must be constructed via keyword expansion. + if isinstance(request, dict): + request = iam_policy.SetIamPolicyRequest(**request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.set_iam_policy, + default_timeout=None, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("resource", request.resource),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + async def get_iam_policy( + self, + request: iam_policy.GetIamPolicyRequest = None, + *, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> policy.Policy: + r"""Gets the IAM access control policy for a function. + Returns an empty policy if the function exists and does + not have a policy set. + Args: + request (:class:`~.iam_policy.GetIamPolicyRequest`): + The request object. Request message 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.Policy: + Defines an Identity and Access Management (IAM) policy. + It is used to specify access control policies for Cloud + Platform resources. + A ``Policy`` is a collection of ``bindings``. A + ``binding`` binds one or more ``members`` to a single + ``role``. Members can be user accounts, service + accounts, Google groups, and domains (such as G Suite). + A ``role`` is a named list of permissions (defined by + IAM or configured by users). A ``binding`` can + optionally specify a ``condition``, which is a logic + expression that further constrains the role binding + based on attributes about the request and/or target + resource. + **JSON Example** + :: + { + "bindings": [ + { + "role": "roles/resourcemanager.organizationAdmin", + "members": [ + "user:mike@example.com", + "group:admins@example.com", + "domain:google.com", + "serviceAccount:my-project-id@appspot.gserviceaccount.com" + ] + }, + { + "role": "roles/resourcemanager.organizationViewer", + "members": ["user:eve@example.com"], + "condition": { + "title": "expirable access", + "description": "Does not grant access after Sep 2020", + "expression": "request.time < + timestamp('2020-10-01T00:00:00.000Z')", + } + } + ] + } + **YAML Example** + :: + bindings: + - members: + - user:mike@example.com + - group:admins@example.com + - domain:google.com + - serviceAccount:my-project-id@appspot.gserviceaccount.com + role: roles/resourcemanager.organizationAdmin + - members: + - user:eve@example.com + role: roles/resourcemanager.organizationViewer + condition: + title: expirable access + description: Does not grant access after Sep 2020 + expression: request.time < timestamp('2020-10-01T00:00:00.000Z') + For a description of IAM and its features, see the `IAM + developer's + guide `__. + """ + # Create or coerce a protobuf request object. + + # The request isn't a proto-plus wrapped type, + # so it must be constructed via keyword expansion. + if isinstance(request, dict): + request = iam_policy.GetIamPolicyRequest(**request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.get_iam_policy, + default_timeout=None, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("resource", request.resource),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + async def test_iam_permissions( + self, + request: iam_policy.TestIamPermissionsRequest = None, + *, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> iam_policy.TestIamPermissionsResponse: + r"""Tests the specified permissions against the IAM access control + policy for a function. If the function does not exist, this will + return an empty set of permissions, not a NOT_FOUND error. + Args: + request (:class:`~.iam_policy.TestIamPermissionsRequest`): + The request object. Request message 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.TestIamPermissionsResponse: + Response message for ``TestIamPermissions`` method. + """ + # Create or coerce a protobuf request object. + + # The request isn't a proto-plus wrapped type, + # so it must be constructed via keyword expansion. + if isinstance(request, dict): + request = iam_policy.TestIamPermissionsRequest(**request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.test_iam_permissions, + default_timeout=None, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("resource", request.resource),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + +try: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution("google-pubsub",).version, + ) +except pkg_resources.DistributionNotFound: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() + + +__all__ = ("SchemaServiceAsyncClient",) diff --git a/google/pubsub_v1/services/schema_service/client.py b/google/pubsub_v1/services/schema_service/client.py new file mode 100644 index 000000000..0468ff993 --- /dev/null +++ b/google/pubsub_v1/services/schema_service/client.py @@ -0,0 +1,1050 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +from distutils import util +import os +import re +from typing import Callable, Dict, Optional, Sequence, Tuple, Type, Union +import pkg_resources + +from google.api_core import client_options as client_options_lib # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport import mtls # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth.exceptions import MutualTLSChannelError # type: ignore +from google.oauth2 import service_account # type: ignore + +from google.iam.v1 import iam_policy_pb2 as iam_policy # type: ignore +from google.iam.v1 import policy_pb2 as policy # type: ignore +from google.pubsub_v1.services.schema_service import pagers +from google.pubsub_v1.types import schema +from google.pubsub_v1.types import schema as gp_schema + +from .transports.base import SchemaServiceTransport, DEFAULT_CLIENT_INFO +from .transports.grpc import SchemaServiceGrpcTransport +from .transports.grpc_asyncio import SchemaServiceGrpcAsyncIOTransport + + +class SchemaServiceClientMeta(type): + """Metaclass for the SchemaService client. + + This provides class-level methods for building and retrieving + support objects (e.g. transport) without polluting the client instance + objects. + """ + + _transport_registry = OrderedDict() # type: Dict[str, Type[SchemaServiceTransport]] + _transport_registry["grpc"] = SchemaServiceGrpcTransport + _transport_registry["grpc_asyncio"] = SchemaServiceGrpcAsyncIOTransport + + def get_transport_class(cls, label: str = None,) -> Type[SchemaServiceTransport]: + """Return an appropriate transport class. + + Args: + label: The name of the desired transport. If none is + provided, then the first transport in the registry is used. + + Returns: + The transport class to use. + """ + # If a specific transport is requested, return that one. + if label: + return cls._transport_registry[label] + + # No transport is requested; return the default (that is, the first one + # in the dictionary). + return next(iter(cls._transport_registry.values())) + + +class SchemaServiceClient(metaclass=SchemaServiceClientMeta): + """ Service for doing schema-related operations. + EXPERIMENTAL: The Schema service is in development and may not + work yet. + """ + + @staticmethod + def _get_default_mtls_endpoint(api_endpoint): + """Convert api endpoint to mTLS endpoint. + Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to + "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively. + Args: + api_endpoint (Optional[str]): the api endpoint to convert. + Returns: + str: converted mTLS api endpoint. + """ + if not api_endpoint: + return api_endpoint + + mtls_endpoint_re = re.compile( + r"(?P[^.]+)(?P\.mtls)?(?P\.sandbox)?(?P\.googleapis\.com)?" + ) + + m = mtls_endpoint_re.match(api_endpoint) + name, mtls, sandbox, googledomain = m.groups() + if mtls or not googledomain: + return api_endpoint + + if sandbox: + return api_endpoint.replace( + "sandbox.googleapis.com", "mtls.sandbox.googleapis.com" + ) + + return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com") + + DEFAULT_ENDPOINT = "pubsub.googleapis.com" + DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__( # type: ignore + DEFAULT_ENDPOINT + ) + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + {@api.name}: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_file(filename) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + + from_service_account_json = from_service_account_file + + @property + def transport(self) -> SchemaServiceTransport: + """Return the transport used by the client instance. + + Returns: + SchemaServiceTransport: The transport used by the client instance. + """ + return self._transport + + @staticmethod + def schema_path(project: str, schema: str,) -> str: + """Return a fully-qualified schema string.""" + return "projects/{project}/schemas/{schema}".format( + project=project, schema=schema, + ) + + @staticmethod + def parse_schema_path(path: str) -> Dict[str, str]: + """Parse a schema path into its component segments.""" + m = re.match(r"^projects/(?P.+?)/schemas/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_billing_account_path(billing_account: str,) -> str: + """Return a fully-qualified billing_account string.""" + return "billingAccounts/{billing_account}".format( + billing_account=billing_account, + ) + + @staticmethod + def parse_common_billing_account_path(path: str) -> Dict[str, str]: + """Parse a billing_account path into its component segments.""" + m = re.match(r"^billingAccounts/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_folder_path(folder: str,) -> str: + """Return a fully-qualified folder string.""" + return "folders/{folder}".format(folder=folder,) + + @staticmethod + def parse_common_folder_path(path: str) -> Dict[str, str]: + """Parse a folder path into its component segments.""" + m = re.match(r"^folders/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_organization_path(organization: str,) -> str: + """Return a fully-qualified organization string.""" + return "organizations/{organization}".format(organization=organization,) + + @staticmethod + def parse_common_organization_path(path: str) -> Dict[str, str]: + """Parse a organization path into its component segments.""" + m = re.match(r"^organizations/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_project_path(project: str,) -> str: + """Return a fully-qualified project string.""" + return "projects/{project}".format(project=project,) + + @staticmethod + def parse_common_project_path(path: str) -> Dict[str, str]: + """Parse a project path into its component segments.""" + m = re.match(r"^projects/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_location_path(project: str, location: str,) -> str: + """Return a fully-qualified location string.""" + return "projects/{project}/locations/{location}".format( + project=project, location=location, + ) + + @staticmethod + def parse_common_location_path(path: str) -> Dict[str, str]: + """Parse a location path into its component segments.""" + m = re.match(r"^projects/(?P.+?)/locations/(?P.+?)$", path) + return m.groupdict() if m else {} + + def __init__( + self, + *, + credentials: Optional[credentials.Credentials] = None, + transport: Union[str, SchemaServiceTransport, None] = None, + client_options: Optional[client_options_lib.ClientOptions] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the schema service client. + + Args: + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + transport (Union[str, ~.SchemaServiceTransport]): The + transport to use. If set to None, a transport is chosen + automatically. + client_options (client_options_lib.ClientOptions): Custom options for the + client. It won't take effect if a ``transport`` instance is provided. + (1) The ``api_endpoint`` property can be used to override the + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT + environment variable can also be used to override the endpoint: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport + creation failed for any reason. + """ + if isinstance(client_options, dict): + client_options = client_options_lib.from_dict(client_options) + if client_options is None: + client_options = client_options_lib.ClientOptions() + + # Create SSL credentials for mutual TLS if needed. + use_client_cert = bool( + util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) + ) + + ssl_credentials = None + is_mtls = False + if use_client_cert: + if client_options.client_cert_source: + import grpc # type: ignore + + cert, key = client_options.client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + is_mtls = True + else: + creds = SslCredentials() + is_mtls = creds.is_mtls + ssl_credentials = creds.ssl_credentials if is_mtls else None + + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + else: + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") + if use_mtls_env == "never": + api_endpoint = self.DEFAULT_ENDPOINT + elif use_mtls_env == "always": + api_endpoint = self.DEFAULT_MTLS_ENDPOINT + elif use_mtls_env == "auto": + api_endpoint = ( + self.DEFAULT_MTLS_ENDPOINT if is_mtls else self.DEFAULT_ENDPOINT + ) + else: + raise MutualTLSChannelError( + "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always" + ) + + # Save or instantiate the transport. + # Ordinarily, we provide the transport, but allowing a custom transport + # instance provides an extensibility point for unusual situations. + if isinstance(transport, SchemaServiceTransport): + # transport is a SchemaServiceTransport instance. + if credentials or client_options.credentials_file: + raise ValueError( + "When providing a transport instance, " + "provide its credentials directly." + ) + if client_options.scopes: + raise ValueError( + "When providing a transport instance, " + "provide its scopes directly." + ) + self._transport = transport + else: + Transport = type(self).get_transport_class(transport) + self._transport = Transport( + credentials=credentials, + credentials_file=client_options.credentials_file, + host=api_endpoint, + scopes=client_options.scopes, + ssl_channel_credentials=ssl_credentials, + quota_project_id=client_options.quota_project_id, + client_info=client_info, + ) + + def create_schema( + self, + request: gp_schema.CreateSchemaRequest = None, + *, + parent: str = None, + schema: gp_schema.Schema = None, + schema_id: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gp_schema.Schema: + r"""Creates a schema. + + Args: + request (:class:`~.gp_schema.CreateSchemaRequest`): + The request object. Request for the CreateSchema method. + parent (:class:`str`): + Required. The name of the project in which to create the + schema. Format is ``projects/{project-id}``. + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + schema (:class:`~.gp_schema.Schema`): + Required. The schema object to create. + + This schema's ``name`` parameter is ignored. The schema + object returned by CreateSchema will have a ``name`` + made using the given ``parent`` and ``schema_id``. + This corresponds to the ``schema`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + schema_id (:class:`str`): + The ID to use for the schema, which will become the + final component of the schema's resource name. + + See + https://cloud.google.com/pubsub/docs/admin#resource_names + for resource name constraints. + This corresponds to the ``schema_id`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.gp_schema.Schema: + A schema resource. + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([parent, schema, schema_id]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a gp_schema.CreateSchemaRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, gp_schema.CreateSchemaRequest): + request = gp_schema.CreateSchemaRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if schema is not None: + request.schema = schema + if schema_id is not None: + request.schema_id = schema_id + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.create_schema] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + def get_schema( + self, + request: schema.GetSchemaRequest = None, + *, + name: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> schema.Schema: + r"""Gets a schema. + + Args: + request (:class:`~.schema.GetSchemaRequest`): + The request object. Request for the GetSchema method. + name (:class:`str`): + Required. The name of the schema to get. Format is + ``projects/{project}/schemas/{schema}``. + This corresponds to the ``name`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.schema.Schema: + A schema resource. + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([name]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a schema.GetSchemaRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, schema.GetSchemaRequest): + request = schema.GetSchemaRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.get_schema] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + def list_schemas( + self, + request: schema.ListSchemasRequest = None, + *, + parent: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pagers.ListSchemasPager: + r"""Lists schemas in a project. + + Args: + request (:class:`~.schema.ListSchemasRequest`): + 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}``. + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pagers.ListSchemasPager: + Response for the ``ListSchemas`` method. + + Iterating over this object will yield results and + resolve additional pages automatically. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([parent]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a schema.ListSchemasRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, schema.ListSchemasRequest): + request = schema.ListSchemasRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.list_schemas] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # This method is paged; wrap the response in a pager, which provides + # an `__iter__` convenience method. + response = pagers.ListSchemasPager( + method=rpc, request=request, response=response, metadata=metadata, + ) + + # Done; return the response. + return response + + def delete_schema( + self, + request: schema.DeleteSchemaRequest = None, + *, + name: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + r"""Deletes a schema. + + Args: + request (:class:`~.schema.DeleteSchemaRequest`): + The request object. Request for the `DeleteSchema` + method. + name (:class:`str`): + Required. Name of the schema to delete. Format is + ``projects/{project}/schemas/{schema}``. + This corresponds to the ``name`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([name]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a schema.DeleteSchemaRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, schema.DeleteSchemaRequest): + request = schema.DeleteSchemaRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.delete_schema] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Send the request. + rpc( + request, retry=retry, timeout=timeout, metadata=metadata, + ) + + def validate_schema( + self, + request: gp_schema.ValidateSchemaRequest = None, + *, + parent: str = None, + schema: gp_schema.Schema = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gp_schema.ValidateSchemaResponse: + r"""Validates a schema. + + Args: + request (:class:`~.gp_schema.ValidateSchemaRequest`): + 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}``. + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + schema (:class:`~.gp_schema.Schema`): + Required. The schema object to + validate. + This corresponds to the ``schema`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.gp_schema.ValidateSchemaResponse: + Response for the ``ValidateSchema`` method. + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([parent, schema]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a gp_schema.ValidateSchemaRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, gp_schema.ValidateSchemaRequest): + request = gp_schema.ValidateSchemaRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if schema is not None: + request.schema = schema + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.validate_schema] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + def validate_message( + self, + request: schema.ValidateMessageRequest = None, + *, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> schema.ValidateMessageResponse: + r"""Validates a message against a schema. + + Args: + request (:class:`~.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. + """ + # Create or coerce a protobuf request object. + + # Minor optimization to avoid making a copy if the user passes + # in a schema.ValidateMessageRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, schema.ValidateMessageRequest): + request = schema.ValidateMessageRequest(request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.validate_message] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + def set_iam_policy( + self, + request: iam_policy.SetIamPolicyRequest = None, + *, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> policy.Policy: + r"""Sets the IAM access control policy on the specified + function. Replaces any existing policy. + Args: + request (:class:`~.iam_policy.SetIamPolicyRequest`): + The request object. Request message 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.Policy: + Defines an Identity and Access Management (IAM) policy. + It is used to specify access control policies for Cloud + Platform resources. + A ``Policy`` is a collection of ``bindings``. A + ``binding`` binds one or more ``members`` to a single + ``role``. Members can be user accounts, service + accounts, Google groups, and domains (such as G Suite). + A ``role`` is a named list of permissions (defined by + IAM or configured by users). A ``binding`` can + optionally specify a ``condition``, which is a logic + expression that further constrains the role binding + based on attributes about the request and/or target + resource. + **JSON Example** + :: + { + "bindings": [ + { + "role": "roles/resourcemanager.organizationAdmin", + "members": [ + "user:mike@example.com", + "group:admins@example.com", + "domain:google.com", + "serviceAccount:my-project-id@appspot.gserviceaccount.com" + ] + }, + { + "role": "roles/resourcemanager.organizationViewer", + "members": ["user:eve@example.com"], + "condition": { + "title": "expirable access", + "description": "Does not grant access after Sep 2020", + "expression": "request.time < + timestamp('2020-10-01T00:00:00.000Z')", + } + } + ] + } + **YAML Example** + :: + bindings: + - members: + - user:mike@example.com + - group:admins@example.com + - domain:google.com + - serviceAccount:my-project-id@appspot.gserviceaccount.com + role: roles/resourcemanager.organizationAdmin + - members: + - user:eve@example.com + role: roles/resourcemanager.organizationViewer + condition: + title: expirable access + description: Does not grant access after Sep 2020 + expression: request.time < timestamp('2020-10-01T00:00:00.000Z') + For a description of IAM and its features, see the `IAM + developer's + guide `__. + """ + # Create or coerce a protobuf request object. + + # The request isn't a proto-plus wrapped type, + # so it must be constructed via keyword expansion. + if isinstance(request, dict): + request = iam_policy.SetIamPolicyRequest(**request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method.wrap_method( + self._transport.set_iam_policy, + default_timeout=None, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("resource", request.resource),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + def get_iam_policy( + self, + request: iam_policy.GetIamPolicyRequest = None, + *, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> policy.Policy: + r"""Gets the IAM access control policy for a function. + Returns an empty policy if the function exists and does + not have a policy set. + Args: + request (:class:`~.iam_policy.GetIamPolicyRequest`): + The request object. Request message 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.Policy: + Defines an Identity and Access Management (IAM) policy. + It is used to specify access control policies for Cloud + Platform resources. + A ``Policy`` is a collection of ``bindings``. A + ``binding`` binds one or more ``members`` to a single + ``role``. Members can be user accounts, service + accounts, Google groups, and domains (such as G Suite). + A ``role`` is a named list of permissions (defined by + IAM or configured by users). A ``binding`` can + optionally specify a ``condition``, which is a logic + expression that further constrains the role binding + based on attributes about the request and/or target + resource. + **JSON Example** + :: + { + "bindings": [ + { + "role": "roles/resourcemanager.organizationAdmin", + "members": [ + "user:mike@example.com", + "group:admins@example.com", + "domain:google.com", + "serviceAccount:my-project-id@appspot.gserviceaccount.com" + ] + }, + { + "role": "roles/resourcemanager.organizationViewer", + "members": ["user:eve@example.com"], + "condition": { + "title": "expirable access", + "description": "Does not grant access after Sep 2020", + "expression": "request.time < + timestamp('2020-10-01T00:00:00.000Z')", + } + } + ] + } + **YAML Example** + :: + bindings: + - members: + - user:mike@example.com + - group:admins@example.com + - domain:google.com + - serviceAccount:my-project-id@appspot.gserviceaccount.com + role: roles/resourcemanager.organizationAdmin + - members: + - user:eve@example.com + role: roles/resourcemanager.organizationViewer + condition: + title: expirable access + description: Does not grant access after Sep 2020 + expression: request.time < timestamp('2020-10-01T00:00:00.000Z') + For a description of IAM and its features, see the `IAM + developer's + guide `__. + """ + # Create or coerce a protobuf request object. + + # The request isn't a proto-plus wrapped type, + # so it must be constructed via keyword expansion. + if isinstance(request, dict): + request = iam_policy.GetIamPolicyRequest(**request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method.wrap_method( + self._transport.get_iam_policy, + default_timeout=None, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("resource", request.resource),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + def test_iam_permissions( + self, + request: iam_policy.TestIamPermissionsRequest = None, + *, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> iam_policy.TestIamPermissionsResponse: + r"""Tests the specified permissions against the IAM access control + policy for a function. If the function does not exist, this will + return an empty set of permissions, not a NOT_FOUND error. + Args: + request (:class:`~.iam_policy.TestIamPermissionsRequest`): + The request object. Request message 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.TestIamPermissionsResponse: + Response message for ``TestIamPermissions`` method. + """ + # Create or coerce a protobuf request object. + + # The request isn't a proto-plus wrapped type, + # so it must be constructed via keyword expansion. + if isinstance(request, dict): + request = iam_policy.TestIamPermissionsRequest(**request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method.wrap_method( + self._transport.test_iam_permissions, + default_timeout=None, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("resource", request.resource),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + +try: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution("google-pubsub",).version, + ) +except pkg_resources.DistributionNotFound: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() + + +__all__ = ("SchemaServiceClient",) diff --git a/google/pubsub_v1/services/schema_service/pagers.py b/google/pubsub_v1/services/schema_service/pagers.py new file mode 100644 index 000000000..e4da22697 --- /dev/null +++ b/google/pubsub_v1/services/schema_service/pagers.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from typing import Any, AsyncIterable, Awaitable, Callable, Iterable, Sequence, Tuple + +from google.pubsub_v1.types import schema + + +class ListSchemasPager: + """A pager for iterating through ``list_schemas`` requests. + + This class thinly wraps an initial + :class:`~.schema.ListSchemasResponse` object, and + provides an ``__iter__`` method to iterate through its + ``schemas`` field. + + If there are more pages, the ``__iter__`` method will make additional + ``ListSchemas`` requests and continue to iterate + through the ``schemas`` field on the + corresponding responses. + + All the usual :class:`~.schema.ListSchemasResponse` + attributes are available on the pager. If multiple requests are made, only + the most recent response is retained, and thus used for attribute lookup. + """ + + def __init__( + self, + method: Callable[..., schema.ListSchemasResponse], + request: schema.ListSchemasRequest, + response: schema.ListSchemasResponse, + *, + metadata: Sequence[Tuple[str, str]] = () + ): + """Instantiate the pager. + + Args: + method (Callable): The method that was originally called, and + which instantiated this pager. + request (:class:`~.schema.ListSchemasRequest`): + The initial request object. + response (:class:`~.schema.ListSchemasResponse`): + The initial response object. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + self._method = method + self._request = schema.ListSchemasRequest(request) + self._response = response + self._metadata = metadata + + def __getattr__(self, name: str) -> Any: + return getattr(self._response, name) + + @property + def pages(self) -> Iterable[schema.ListSchemasResponse]: + yield self._response + while self._response.next_page_token: + self._request.page_token = self._response.next_page_token + self._response = self._method(self._request, metadata=self._metadata) + yield self._response + + def __iter__(self) -> Iterable[schema.Schema]: + for page in self.pages: + yield from page.schemas + + def __repr__(self) -> str: + return "{0}<{1!r}>".format(self.__class__.__name__, self._response) + + +class ListSchemasAsyncPager: + """A pager for iterating through ``list_schemas`` requests. + + This class thinly wraps an initial + :class:`~.schema.ListSchemasResponse` object, and + provides an ``__aiter__`` method to iterate through its + ``schemas`` field. + + If there are more pages, the ``__aiter__`` method will make additional + ``ListSchemas`` requests and continue to iterate + through the ``schemas`` field on the + corresponding responses. + + All the usual :class:`~.schema.ListSchemasResponse` + attributes are available on the pager. If multiple requests are made, only + the most recent response is retained, and thus used for attribute lookup. + """ + + def __init__( + self, + method: Callable[..., Awaitable[schema.ListSchemasResponse]], + request: schema.ListSchemasRequest, + response: schema.ListSchemasResponse, + *, + metadata: Sequence[Tuple[str, str]] = () + ): + """Instantiate the pager. + + Args: + method (Callable): The method that was originally called, and + which instantiated this pager. + request (:class:`~.schema.ListSchemasRequest`): + The initial request object. + response (:class:`~.schema.ListSchemasResponse`): + The initial response object. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + self._method = method + self._request = schema.ListSchemasRequest(request) + self._response = response + self._metadata = metadata + + def __getattr__(self, name: str) -> Any: + return getattr(self._response, name) + + @property + async def pages(self) -> AsyncIterable[schema.ListSchemasResponse]: + yield self._response + while self._response.next_page_token: + self._request.page_token = self._response.next_page_token + self._response = await self._method(self._request, metadata=self._metadata) + yield self._response + + def __aiter__(self) -> AsyncIterable[schema.Schema]: + async def async_generator(): + async for page in self.pages: + for response in page.schemas: + yield response + + return async_generator() + + def __repr__(self) -> str: + return "{0}<{1!r}>".format(self.__class__.__name__, self._response) diff --git a/google/pubsub_v1/services/schema_service/transports/__init__.py b/google/pubsub_v1/services/schema_service/transports/__init__.py new file mode 100644 index 000000000..015410d3d --- /dev/null +++ b/google/pubsub_v1/services/schema_service/transports/__init__.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +from typing import Dict, Type + +from .base import SchemaServiceTransport +from .grpc import SchemaServiceGrpcTransport +from .grpc_asyncio import SchemaServiceGrpcAsyncIOTransport + + +# Compile a registry of transports. +_transport_registry = OrderedDict() # type: Dict[str, Type[SchemaServiceTransport]] +_transport_registry["grpc"] = SchemaServiceGrpcTransport +_transport_registry["grpc_asyncio"] = SchemaServiceGrpcAsyncIOTransport + +__all__ = ( + "SchemaServiceTransport", + "SchemaServiceGrpcTransport", + "SchemaServiceGrpcAsyncIOTransport", +) diff --git a/google/pubsub_v1/services/schema_service/transports/base.py b/google/pubsub_v1/services/schema_service/transports/base.py new file mode 100644 index 000000000..01cf04c9e --- /dev/null +++ b/google/pubsub_v1/services/schema_service/transports/base.py @@ -0,0 +1,227 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import abc +import typing +import pkg_resources + +from google import auth # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.auth import credentials # type: ignore + +from google.iam.v1 import iam_policy_pb2 as iam_policy # type: ignore +from google.iam.v1 import policy_pb2 as policy # type: ignore +from google.protobuf import empty_pb2 as empty # type: ignore +from google.pubsub_v1.types import schema +from google.pubsub_v1.types import schema as gp_schema + + +try: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution("google-pubsub",).version, + ) +except pkg_resources.DistributionNotFound: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() + + +class SchemaServiceTransport(abc.ABC): + """Abstract transport class for SchemaService.""" + + AUTH_SCOPES = ( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/pubsub", + ) + + def __init__( + self, + *, + host: str = "pubsub.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: typing.Optional[str] = None, + scopes: typing.Optional[typing.Sequence[str]] = AUTH_SCOPES, + quota_project_id: typing.Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + **kwargs, + ) -> 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 mutually exclusive with credentials. + scope (Optional[Sequence[str]]): A list of scopes. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + """ + # Save the hostname. Default to port 443 (HTTPS) if none is specified. + if ":" not in host: + host += ":443" + self._host = host + + # If no credentials are provided, then determine the appropriate + # defaults. + if credentials and credentials_file: + raise exceptions.DuplicateCredentialArgs( + "'credentials_file' and 'credentials' are mutually exclusive" + ) + + if credentials_file is not None: + credentials, _ = auth.load_credentials_from_file( + credentials_file, scopes=scopes, quota_project_id=quota_project_id + ) + + elif credentials is None: + credentials, _ = auth.default( + scopes=scopes, quota_project_id=quota_project_id + ) + + # Save the credentials. + self._credentials = credentials + + # Lifted into its own function so it can be stubbed out during tests. + self._prep_wrapped_messages(client_info) + + def _prep_wrapped_messages(self, client_info): + # Precompute the wrapped methods. + self._wrapped_methods = { + self.create_schema: gapic_v1.method.wrap_method( + self.create_schema, default_timeout=None, client_info=client_info, + ), + self.get_schema: gapic_v1.method.wrap_method( + self.get_schema, default_timeout=None, client_info=client_info, + ), + self.list_schemas: gapic_v1.method.wrap_method( + self.list_schemas, default_timeout=None, client_info=client_info, + ), + self.delete_schema: gapic_v1.method.wrap_method( + self.delete_schema, default_timeout=None, client_info=client_info, + ), + self.validate_schema: gapic_v1.method.wrap_method( + self.validate_schema, default_timeout=None, client_info=client_info, + ), + self.validate_message: gapic_v1.method.wrap_method( + self.validate_message, default_timeout=None, client_info=client_info, + ), + } + + @property + def create_schema( + self, + ) -> typing.Callable[ + [gp_schema.CreateSchemaRequest], + typing.Union[gp_schema.Schema, typing.Awaitable[gp_schema.Schema]], + ]: + raise NotImplementedError() + + @property + def get_schema( + self, + ) -> typing.Callable[ + [schema.GetSchemaRequest], + typing.Union[schema.Schema, typing.Awaitable[schema.Schema]], + ]: + raise NotImplementedError() + + @property + def list_schemas( + self, + ) -> typing.Callable[ + [schema.ListSchemasRequest], + typing.Union[ + schema.ListSchemasResponse, typing.Awaitable[schema.ListSchemasResponse] + ], + ]: + raise NotImplementedError() + + @property + def delete_schema( + self, + ) -> typing.Callable[ + [schema.DeleteSchemaRequest], + typing.Union[empty.Empty, typing.Awaitable[empty.Empty]], + ]: + raise NotImplementedError() + + @property + def validate_schema( + self, + ) -> typing.Callable[ + [gp_schema.ValidateSchemaRequest], + typing.Union[ + gp_schema.ValidateSchemaResponse, + typing.Awaitable[gp_schema.ValidateSchemaResponse], + ], + ]: + raise NotImplementedError() + + @property + def validate_message( + self, + ) -> typing.Callable[ + [schema.ValidateMessageRequest], + typing.Union[ + schema.ValidateMessageResponse, + typing.Awaitable[schema.ValidateMessageResponse], + ], + ]: + raise NotImplementedError() + + @property + def set_iam_policy( + self, + ) -> typing.Callable[ + [iam_policy.SetIamPolicyRequest], + typing.Union[policy.Policy, typing.Awaitable[policy.Policy]], + ]: + raise NotImplementedError() + + @property + def get_iam_policy( + self, + ) -> typing.Callable[ + [iam_policy.GetIamPolicyRequest], + typing.Union[policy.Policy, typing.Awaitable[policy.Policy]], + ]: + raise NotImplementedError() + + @property + def test_iam_permissions( + self, + ) -> typing.Callable[ + [iam_policy.TestIamPermissionsRequest], + typing.Union[ + iam_policy.TestIamPermissionsResponse, + typing.Awaitable[iam_policy.TestIamPermissionsResponse], + ], + ]: + raise NotImplementedError() + + +__all__ = ("SchemaServiceTransport",) diff --git a/google/pubsub_v1/services/schema_service/transports/grpc.py b/google/pubsub_v1/services/schema_service/transports/grpc.py new file mode 100644 index 000000000..02f91d358 --- /dev/null +++ b/google/pubsub_v1/services/schema_service/transports/grpc.py @@ -0,0 +1,480 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import warnings +from typing import Callable, Dict, Optional, Sequence, Tuple + +from google.api_core import grpc_helpers # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google import auth # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore + +import grpc # type: ignore + +from google.iam.v1 import iam_policy_pb2 as iam_policy # type: ignore +from google.iam.v1 import policy_pb2 as policy # type: ignore +from google.protobuf import empty_pb2 as empty # 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 + + +class SchemaServiceGrpcTransport(SchemaServiceTransport): + """gRPC backend transport for SchemaService. + + Service for doing schema-related operations. + EXPERIMENTAL: The Schema service is in development and may not + work yet. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends protocol buffers over the wire using gRPC (which is built on + top of HTTP/2); the ``grpcio`` package must be installed. + """ + + _stubs: Dict[str, Callable] + + def __init__( + self, + *, + host: str = "pubsub.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: str = None, + scopes: Sequence[str] = None, + channel: grpc.Channel = None, + api_mtls_endpoint: str = None, + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> 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. + This argument is ignored if ``channel`` is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + channel (Optional[grpc.Channel]): A ``Channel`` instance through + which to make calls. + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create + a mutual TLS channel with client SSL credentials from + ``client_cert_source`` or applicatin default SSL credentials. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc 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're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport + creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + self._ssl_channel_credentials = ssl_channel_credentials + + if channel: + # Sanity check: Ensure that channel and credentials are not both + # provided. + credentials = False + + # If a channel was explicitly provided, set it. + self._grpc_channel = channel + self._ssl_channel_credentials = None + elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + + host = ( + api_mtls_endpoint + if ":" in api_mtls_endpoint + else api_mtls_endpoint + ":443" + ) + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + ssl_credentials = SslCredentials().ssl_credentials + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ("grpc.keepalive_time_ms", 30000), + ], + ) + self._ssl_channel_credentials = ssl_credentials + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ("grpc.keepalive_time_ms", 30000), + ], + ) + + self._stubs = {} # type: Dict[str, Callable] + + # Run the base constructor. + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + client_info=client_info, + ) + + @classmethod + def create_channel( + cls, + host: str = "pubsub.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: str = None, + scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, + **kwargs, + ) -> grpc.Channel: + """Create and return a gRPC channel object. + Args: + address (Optional[str]): The host for the channel to use. + credentials (Optional[~.Credentials]): The + authorization credentials to attach to requests. These + credentials identify this application to the service. If + none are specified, the client will attempt to ascertain + the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + kwargs (Optional[dict]): Keyword arguments, which are passed to the + channel creation. + Returns: + grpc.Channel: A gRPC channel object. + + Raises: + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + scopes = scopes or cls.AUTH_SCOPES + return grpc_helpers.create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + **kwargs, + ) + + @property + def grpc_channel(self) -> grpc.Channel: + """Return the channel designed to connect to this service. + """ + return self._grpc_channel + + @property + def create_schema( + self, + ) -> Callable[[gp_schema.CreateSchemaRequest], gp_schema.Schema]: + r"""Return a callable for the create schema method over gRPC. + + Creates a schema. + + Returns: + Callable[[~.CreateSchemaRequest], + ~.Schema]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "create_schema" not in self._stubs: + self._stubs["create_schema"] = self.grpc_channel.unary_unary( + "/google.pubsub.v1.SchemaService/CreateSchema", + request_serializer=gp_schema.CreateSchemaRequest.serialize, + response_deserializer=gp_schema.Schema.deserialize, + ) + return self._stubs["create_schema"] + + @property + def get_schema(self) -> Callable[[schema.GetSchemaRequest], schema.Schema]: + r"""Return a callable for the get schema method over gRPC. + + Gets a schema. + + Returns: + Callable[[~.GetSchemaRequest], + ~.Schema]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "get_schema" not in self._stubs: + self._stubs["get_schema"] = self.grpc_channel.unary_unary( + "/google.pubsub.v1.SchemaService/GetSchema", + request_serializer=schema.GetSchemaRequest.serialize, + response_deserializer=schema.Schema.deserialize, + ) + return self._stubs["get_schema"] + + @property + def list_schemas( + self, + ) -> Callable[[schema.ListSchemasRequest], schema.ListSchemasResponse]: + r"""Return a callable for the list schemas method over gRPC. + + Lists schemas in a project. + + Returns: + Callable[[~.ListSchemasRequest], + ~.ListSchemasResponse]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "list_schemas" not in self._stubs: + self._stubs["list_schemas"] = self.grpc_channel.unary_unary( + "/google.pubsub.v1.SchemaService/ListSchemas", + request_serializer=schema.ListSchemasRequest.serialize, + response_deserializer=schema.ListSchemasResponse.deserialize, + ) + return self._stubs["list_schemas"] + + @property + def delete_schema(self) -> Callable[[schema.DeleteSchemaRequest], empty.Empty]: + r"""Return a callable for the delete schema method over gRPC. + + Deletes a schema. + + Returns: + Callable[[~.DeleteSchemaRequest], + ~.Empty]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "delete_schema" not in self._stubs: + self._stubs["delete_schema"] = self.grpc_channel.unary_unary( + "/google.pubsub.v1.SchemaService/DeleteSchema", + request_serializer=schema.DeleteSchemaRequest.serialize, + response_deserializer=empty.Empty.FromString, + ) + return self._stubs["delete_schema"] + + @property + def validate_schema( + self, + ) -> Callable[[gp_schema.ValidateSchemaRequest], gp_schema.ValidateSchemaResponse]: + r"""Return a callable for the validate schema method over gRPC. + + Validates a schema. + + Returns: + Callable[[~.ValidateSchemaRequest], + ~.ValidateSchemaResponse]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "validate_schema" not in self._stubs: + self._stubs["validate_schema"] = self.grpc_channel.unary_unary( + "/google.pubsub.v1.SchemaService/ValidateSchema", + request_serializer=gp_schema.ValidateSchemaRequest.serialize, + response_deserializer=gp_schema.ValidateSchemaResponse.deserialize, + ) + return self._stubs["validate_schema"] + + @property + def validate_message( + self, + ) -> Callable[[schema.ValidateMessageRequest], schema.ValidateMessageResponse]: + r"""Return a callable for the validate message method over gRPC. + + Validates a message against a schema. + + Returns: + Callable[[~.ValidateMessageRequest], + ~.ValidateMessageResponse]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "validate_message" not in self._stubs: + self._stubs["validate_message"] = self.grpc_channel.unary_unary( + "/google.pubsub.v1.SchemaService/ValidateMessage", + request_serializer=schema.ValidateMessageRequest.serialize, + response_deserializer=schema.ValidateMessageResponse.deserialize, + ) + return self._stubs["validate_message"] + + @property + def set_iam_policy( + self, + ) -> Callable[[iam_policy.SetIamPolicyRequest], policy.Policy]: + r"""Return a callable for the set iam policy method over gRPC. + Sets the IAM access control policy on the specified + function. Replaces any existing policy. + Returns: + Callable[[~.SetIamPolicyRequest], + ~.Policy]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "set_iam_policy" not in self._stubs: + self._stubs["set_iam_policy"] = self.grpc_channel.unary_unary( + "/google.iam.v1.IAMPolicy/SetIamPolicy", + request_serializer=iam_policy.SetIamPolicyRequest.SerializeToString, + response_deserializer=policy.Policy.FromString, + ) + return self._stubs["set_iam_policy"] + + @property + def get_iam_policy( + self, + ) -> Callable[[iam_policy.GetIamPolicyRequest], policy.Policy]: + r"""Return a callable for the get iam policy method over gRPC. + Gets the IAM access control policy for a function. + Returns an empty policy if the function exists and does + not have a policy set. + Returns: + Callable[[~.GetIamPolicyRequest], + ~.Policy]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "get_iam_policy" not in self._stubs: + self._stubs["get_iam_policy"] = self.grpc_channel.unary_unary( + "/google.iam.v1.IAMPolicy/GetIamPolicy", + request_serializer=iam_policy.GetIamPolicyRequest.SerializeToString, + response_deserializer=policy.Policy.FromString, + ) + return self._stubs["get_iam_policy"] + + @property + def test_iam_permissions( + self, + ) -> Callable[ + [iam_policy.TestIamPermissionsRequest], iam_policy.TestIamPermissionsResponse + ]: + r"""Return a callable for the test iam permissions method over gRPC. + Tests the specified permissions against the IAM access control + policy for a function. If the function does not exist, this will + return an empty set of permissions, not a NOT_FOUND error. + Returns: + Callable[[~.TestIamPermissionsRequest], + ~.TestIamPermissionsResponse]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "test_iam_permissions" not in self._stubs: + self._stubs["test_iam_permissions"] = self.grpc_channel.unary_unary( + "/google.iam.v1.IAMPolicy/TestIamPermissions", + request_serializer=iam_policy.TestIamPermissionsRequest.SerializeToString, + response_deserializer=iam_policy.TestIamPermissionsResponse.FromString, + ) + return self._stubs["test_iam_permissions"] + + +__all__ = ("SchemaServiceGrpcTransport",) diff --git a/google/pubsub_v1/services/schema_service/transports/grpc_asyncio.py b/google/pubsub_v1/services/schema_service/transports/grpc_asyncio.py new file mode 100644 index 000000000..3455c207b --- /dev/null +++ b/google/pubsub_v1/services/schema_service/transports/grpc_asyncio.py @@ -0,0 +1,493 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import warnings +from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple + +from google.api_core import gapic_v1 # type: ignore +from google.api_core import grpc_helpers_async # type: ignore +from google import auth # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore + +import grpc # type: ignore +from grpc.experimental import aio # type: ignore + +from google.iam.v1 import iam_policy_pb2 as iam_policy # type: ignore +from google.iam.v1 import policy_pb2 as policy # type: ignore +from google.protobuf import empty_pb2 as empty # 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 +from .grpc import SchemaServiceGrpcTransport + + +class SchemaServiceGrpcAsyncIOTransport(SchemaServiceTransport): + """gRPC AsyncIO backend transport for SchemaService. + + Service for doing schema-related operations. + EXPERIMENTAL: The Schema service is in development and may not + work yet. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends protocol buffers over the wire using gRPC (which is built on + top of HTTP/2); the ``grpcio`` package must be installed. + """ + + _grpc_channel: aio.Channel + _stubs: Dict[str, Callable] = {} + + @classmethod + def create_channel( + cls, + host: str = "pubsub.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, + **kwargs, + ) -> aio.Channel: + """Create and return a gRPC AsyncIO channel object. + Args: + address (Optional[str]): The host for the channel to use. + credentials (Optional[~.Credentials]): The + authorization credentials to attach to requests. These + credentials identify this application to the service. If + none are specified, the client will attempt to ascertain + the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + kwargs (Optional[dict]): Keyword arguments, which are passed to the + channel creation. + Returns: + aio.Channel: A gRPC AsyncIO channel object. + """ + scopes = scopes or cls.AUTH_SCOPES + return grpc_helpers_async.create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + **kwargs, + ) + + def __init__( + self, + *, + host: str = "pubsub.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + channel: aio.Channel = None, + api_mtls_endpoint: str = None, + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, + quota_project_id=None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> 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. + This argument is ignored if ``channel`` is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + channel (Optional[aio.Channel]): A ``Channel`` instance through + which to make calls. + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create + a mutual TLS channel with client SSL credentials from + ``client_cert_source`` or applicatin default SSL credentials. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc 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're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + self._ssl_channel_credentials = ssl_channel_credentials + + if channel: + # Sanity check: Ensure that channel and credentials are not both + # provided. + credentials = False + + # If a channel was explicitly provided, set it. + self._grpc_channel = channel + self._ssl_channel_credentials = None + elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + + host = ( + api_mtls_endpoint + if ":" in api_mtls_endpoint + else api_mtls_endpoint + ":443" + ) + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + ssl_credentials = SslCredentials().ssl_credentials + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ("grpc.keepalive_time_ms", 30000), + ], + ) + self._ssl_channel_credentials = ssl_credentials + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ("grpc.keepalive_time_ms", 30000), + ], + ) + + # Run the base constructor. + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + client_info=client_info, + ) + + self._stubs = {} + + @property + def grpc_channel(self) -> aio.Channel: + """Create the channel designed to connect to this service. + + This property caches on the instance; repeated calls return + the same channel. + """ + # Return the channel from cache. + return self._grpc_channel + + @property + def create_schema( + self, + ) -> Callable[[gp_schema.CreateSchemaRequest], Awaitable[gp_schema.Schema]]: + r"""Return a callable for the create schema method over gRPC. + + Creates a schema. + + Returns: + Callable[[~.CreateSchemaRequest], + Awaitable[~.Schema]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "create_schema" not in self._stubs: + self._stubs["create_schema"] = self.grpc_channel.unary_unary( + "/google.pubsub.v1.SchemaService/CreateSchema", + request_serializer=gp_schema.CreateSchemaRequest.serialize, + response_deserializer=gp_schema.Schema.deserialize, + ) + return self._stubs["create_schema"] + + @property + def get_schema( + self, + ) -> Callable[[schema.GetSchemaRequest], Awaitable[schema.Schema]]: + r"""Return a callable for the get schema method over gRPC. + + Gets a schema. + + Returns: + Callable[[~.GetSchemaRequest], + Awaitable[~.Schema]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "get_schema" not in self._stubs: + self._stubs["get_schema"] = self.grpc_channel.unary_unary( + "/google.pubsub.v1.SchemaService/GetSchema", + request_serializer=schema.GetSchemaRequest.serialize, + response_deserializer=schema.Schema.deserialize, + ) + return self._stubs["get_schema"] + + @property + def list_schemas( + self, + ) -> Callable[[schema.ListSchemasRequest], Awaitable[schema.ListSchemasResponse]]: + r"""Return a callable for the list schemas method over gRPC. + + Lists schemas in a project. + + Returns: + Callable[[~.ListSchemasRequest], + Awaitable[~.ListSchemasResponse]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "list_schemas" not in self._stubs: + self._stubs["list_schemas"] = self.grpc_channel.unary_unary( + "/google.pubsub.v1.SchemaService/ListSchemas", + request_serializer=schema.ListSchemasRequest.serialize, + response_deserializer=schema.ListSchemasResponse.deserialize, + ) + return self._stubs["list_schemas"] + + @property + def delete_schema( + self, + ) -> Callable[[schema.DeleteSchemaRequest], Awaitable[empty.Empty]]: + r"""Return a callable for the delete schema method over gRPC. + + Deletes a schema. + + Returns: + Callable[[~.DeleteSchemaRequest], + Awaitable[~.Empty]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "delete_schema" not in self._stubs: + self._stubs["delete_schema"] = self.grpc_channel.unary_unary( + "/google.pubsub.v1.SchemaService/DeleteSchema", + request_serializer=schema.DeleteSchemaRequest.serialize, + response_deserializer=empty.Empty.FromString, + ) + return self._stubs["delete_schema"] + + @property + def validate_schema( + self, + ) -> Callable[ + [gp_schema.ValidateSchemaRequest], Awaitable[gp_schema.ValidateSchemaResponse] + ]: + r"""Return a callable for the validate schema method over gRPC. + + Validates a schema. + + Returns: + Callable[[~.ValidateSchemaRequest], + Awaitable[~.ValidateSchemaResponse]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "validate_schema" not in self._stubs: + self._stubs["validate_schema"] = self.grpc_channel.unary_unary( + "/google.pubsub.v1.SchemaService/ValidateSchema", + request_serializer=gp_schema.ValidateSchemaRequest.serialize, + response_deserializer=gp_schema.ValidateSchemaResponse.deserialize, + ) + return self._stubs["validate_schema"] + + @property + def validate_message( + self, + ) -> Callable[ + [schema.ValidateMessageRequest], Awaitable[schema.ValidateMessageResponse] + ]: + r"""Return a callable for the validate message method over gRPC. + + Validates a message against a schema. + + Returns: + Callable[[~.ValidateMessageRequest], + Awaitable[~.ValidateMessageResponse]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "validate_message" not in self._stubs: + self._stubs["validate_message"] = self.grpc_channel.unary_unary( + "/google.pubsub.v1.SchemaService/ValidateMessage", + request_serializer=schema.ValidateMessageRequest.serialize, + response_deserializer=schema.ValidateMessageResponse.deserialize, + ) + return self._stubs["validate_message"] + + @property + def set_iam_policy( + self, + ) -> Callable[[iam_policy.SetIamPolicyRequest], Awaitable[policy.Policy]]: + r"""Return a callable for the set iam policy method over gRPC. + Sets the IAM access control policy on the specified + function. Replaces any existing policy. + Returns: + Callable[[~.SetIamPolicyRequest], + Awaitable[~.Policy]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "set_iam_policy" not in self._stubs: + self._stubs["set_iam_policy"] = self.grpc_channel.unary_unary( + "/google.iam.v1.IAMPolicy/SetIamPolicy", + request_serializer=iam_policy.SetIamPolicyRequest.SerializeToString, + response_deserializer=policy.Policy.FromString, + ) + return self._stubs["set_iam_policy"] + + @property + def get_iam_policy( + self, + ) -> Callable[[iam_policy.GetIamPolicyRequest], Awaitable[policy.Policy]]: + r"""Return a callable for the get iam policy method over gRPC. + Gets the IAM access control policy for a function. + Returns an empty policy if the function exists and does + not have a policy set. + Returns: + Callable[[~.GetIamPolicyRequest], + Awaitable[~.Policy]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "get_iam_policy" not in self._stubs: + self._stubs["get_iam_policy"] = self.grpc_channel.unary_unary( + "/google.iam.v1.IAMPolicy/GetIamPolicy", + request_serializer=iam_policy.GetIamPolicyRequest.SerializeToString, + response_deserializer=policy.Policy.FromString, + ) + return self._stubs["get_iam_policy"] + + @property + def test_iam_permissions( + self, + ) -> Callable[ + [iam_policy.TestIamPermissionsRequest], + Awaitable[iam_policy.TestIamPermissionsResponse], + ]: + r"""Return a callable for the test iam permissions method over gRPC. + Tests the specified permissions against the IAM access control + policy for a function. If the function does not exist, this will + return an empty set of permissions, not a NOT_FOUND error. + Returns: + Callable[[~.TestIamPermissionsRequest], + Awaitable[~.TestIamPermissionsResponse]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "test_iam_permissions" not in self._stubs: + self._stubs["test_iam_permissions"] = self.grpc_channel.unary_unary( + "/google.iam.v1.IAMPolicy/TestIamPermissions", + request_serializer=iam_policy.TestIamPermissionsRequest.SerializeToString, + response_deserializer=iam_policy.TestIamPermissionsResponse.FromString, + ) + return self._stubs["test_iam_permissions"] + + +__all__ = ("SchemaServiceGrpcAsyncIOTransport",) diff --git a/google/pubsub_v1/services/subscriber/async_client.py b/google/pubsub_v1/services/subscriber/async_client.py index 61d79ce8b..76d5204be 100644 --- a/google/pubsub_v1/services/subscriber/async_client.py +++ b/google/pubsub_v1/services/subscriber/async_client.py @@ -1547,8 +1547,8 @@ async def seek( ) -> pubsub.SeekResponse: r"""Seeks an existing subscription to a point in time or to a given snapshot, whichever is provided in the request. Snapshots are - used in - `Seek `__ + used in [Seek] + (https://cloud.google.com/pubsub/docs/replay-overview) 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. diff --git a/google/pubsub_v1/services/subscriber/client.py b/google/pubsub_v1/services/subscriber/client.py index 60d44b579..0e87d6a2d 100644 --- a/google/pubsub_v1/services/subscriber/client.py +++ b/google/pubsub_v1/services/subscriber/client.py @@ -1686,8 +1686,8 @@ def seek( ) -> pubsub.SeekResponse: r"""Seeks an existing subscription to a point in time or to a given snapshot, whichever is provided in the request. Snapshots are - used in - `Seek `__ + used in [Seek] + (https://cloud.google.com/pubsub/docs/replay-overview) 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. diff --git a/google/pubsub_v1/services/subscriber/transports/grpc.py b/google/pubsub_v1/services/subscriber/transports/grpc.py index 7b86cb631..1be01d024 100644 --- a/google/pubsub_v1/services/subscriber/transports/grpc.py +++ b/google/pubsub_v1/services/subscriber/transports/grpc.py @@ -155,6 +155,7 @@ def __init__( options=[ ("grpc.max_send_message_length", -1), ("grpc.max_receive_message_length", -1), + ("grpc.keepalive_time_ms", 30000), ], ) self._ssl_channel_credentials = ssl_credentials @@ -177,6 +178,7 @@ def __init__( options=[ ("grpc.max_send_message_length", -1), ("grpc.max_receive_message_length", -1), + ("grpc.keepalive_time_ms", 30000), ], ) @@ -720,8 +722,8 @@ def seek(self) -> Callable[[pubsub.SeekRequest], pubsub.SeekResponse]: Seeks an existing subscription to a point in time or to a given snapshot, whichever is provided in the request. Snapshots are - used in - `Seek `__ + used in [Seek] + (https://cloud.google.com/pubsub/docs/replay-overview) 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. diff --git a/google/pubsub_v1/services/subscriber/transports/grpc_asyncio.py b/google/pubsub_v1/services/subscriber/transports/grpc_asyncio.py index 307391dc2..fa89c11bc 100644 --- a/google/pubsub_v1/services/subscriber/transports/grpc_asyncio.py +++ b/google/pubsub_v1/services/subscriber/transports/grpc_asyncio.py @@ -200,6 +200,7 @@ def __init__( options=[ ("grpc.max_send_message_length", -1), ("grpc.max_receive_message_length", -1), + ("grpc.keepalive_time_ms", 30000), ], ) self._ssl_channel_credentials = ssl_credentials @@ -222,6 +223,7 @@ def __init__( options=[ ("grpc.max_send_message_length", -1), ("grpc.max_receive_message_length", -1), + ("grpc.keepalive_time_ms", 30000), ], ) @@ -736,8 +738,8 @@ def seek(self) -> Callable[[pubsub.SeekRequest], Awaitable[pubsub.SeekResponse]] Seeks an existing subscription to a point in time or to a given snapshot, whichever is provided in the request. Snapshots are - used in - `Seek `__ + used in [Seek] + (https://cloud.google.com/pubsub/docs/replay-overview) 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. diff --git a/google/pubsub_v1/types/__init__.py b/google/pubsub_v1/types/__init__.py index 3b21183de..afff7e6df 100644 --- a/google/pubsub_v1/types/__init__.py +++ b/google/pubsub_v1/types/__init__.py @@ -15,8 +15,23 @@ # limitations under the License. # +from .schema import ( + Schema, + CreateSchemaRequest, + GetSchemaRequest, + ListSchemasRequest, + ListSchemasResponse, + DeleteSchemaRequest, + ValidateSchemaRequest, + ValidateSchemaResponse, + ValidateMessageRequest, + ValidateMessageResponse, + SchemaView, + Encoding, +) from .pubsub import ( MessageStoragePolicy, + SchemaSettings, Topic, PubsubMessage, GetTopicRequest, @@ -62,7 +77,20 @@ ) __all__ = ( + "Schema", + "CreateSchemaRequest", + "GetSchemaRequest", + "ListSchemasRequest", + "ListSchemasResponse", + "DeleteSchemaRequest", + "ValidateSchemaRequest", + "ValidateSchemaResponse", + "ValidateMessageRequest", + "ValidateMessageResponse", + "SchemaView", + "Encoding", "MessageStoragePolicy", + "SchemaSettings", "Topic", "PubsubMessage", "GetTopicRequest", diff --git a/google/pubsub_v1/types/pubsub.py b/google/pubsub_v1/types/pubsub.py index c34cf422d..39563bb58 100644 --- a/google/pubsub_v1/types/pubsub.py +++ b/google/pubsub_v1/types/pubsub.py @@ -21,12 +21,14 @@ from google.protobuf import duration_pb2 as duration # type: ignore from google.protobuf import field_mask_pb2 as field_mask # type: ignore from google.protobuf import timestamp_pb2 as timestamp # type: ignore +from google.pubsub_v1.types import schema as gp_schema __protobuf__ = proto.module( package="google.pubsub.v1", manifest={ "MessageStoragePolicy", + "SchemaSettings", "Topic", "PubsubMessage", "GetTopicRequest", @@ -92,6 +94,25 @@ class MessageStoragePolicy(proto.Message): allowed_persistence_regions = proto.RepeatedField(proto.STRING, number=1) +class SchemaSettings(proto.Message): + r"""Settings for validating messages published against a schema. + + Attributes: + schema (str): + Required. The name of the schema that messages published + should be validated against. Format is + ``projects/{project}/schemas/{schema}``. The value of this + field will be ``_deleted-schema_`` if the schema has been + deleted. + encoding (~.gp_schema.Encoding): + The encoding of messages validated against ``schema``. + """ + + schema = proto.Field(proto.STRING, number=1) + + encoding = proto.Field(proto.ENUM, number=2, enum=gp_schema.Encoding,) + + class Topic(proto.Message): r"""A topic resource. @@ -119,6 +140,15 @@ class Topic(proto.Message): The expected format is ``projects/*/locations/*/keyRings/*/cryptoKeys/*``. + schema_settings (~.pubsub.SchemaSettings): + Settings for validating messages published + against a schema. + EXPERIMENTAL: Schema support is in development + and may not work yet. + satisfies_pzs (bool): + Reserved for future use. This field is set + only in responses from the server; it is ignored + if it is set in any requests. """ name = proto.Field(proto.STRING, number=1) @@ -131,6 +161,10 @@ class Topic(proto.Message): kms_key_name = proto.Field(proto.STRING, number=5) + schema_settings = proto.Field(proto.MESSAGE, number=6, message="SchemaSettings",) + + satisfies_pzs = proto.Field(proto.BOOL, number=7) + class PubsubMessage(proto.Message): r"""A message that is published by publishers and consumed by diff --git a/google/pubsub_v1/types/schema.py b/google/pubsub_v1/types/schema.py new file mode 100644 index 000000000..2efa667c1 --- /dev/null +++ b/google/pubsub_v1/types/schema.py @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import proto # type: ignore + + +__protobuf__ = proto.module( + package="google.pubsub.v1", + manifest={ + "SchemaView", + "Encoding", + "Schema", + "CreateSchemaRequest", + "GetSchemaRequest", + "ListSchemasRequest", + "ListSchemasResponse", + "DeleteSchemaRequest", + "ValidateSchemaRequest", + "ValidateSchemaResponse", + "ValidateMessageRequest", + "ValidateMessageResponse", + }, +) + + +class SchemaView(proto.Enum): + r"""View of Schema object fields to be returned by GetSchema and + ListSchemas. + """ + SCHEMA_VIEW_UNSPECIFIED = 0 + BASIC = 1 + FULL = 2 + + +class Encoding(proto.Enum): + r"""Possible encoding types for messages.""" + ENCODING_UNSPECIFIED = 0 + JSON = 1 + BINARY = 2 + + +class Schema(proto.Message): + r"""A schema resource. + + Attributes: + name (str): + Required. Name of the schema. Format is + ``projects/{project}/schemas/{schema}``. + type_ (~.gp_schema.Schema.Type): + The type of the schema definition. + definition (str): + The definition of the schema. This should contain a string + representing the full definition of the schema that is a + valid schema definition of the type specified in ``type``. + """ + + class Type(proto.Enum): + r"""Possible schema definition types.""" + TYPE_UNSPECIFIED = 0 + PROTOCOL_BUFFER = 1 + AVRO = 2 + + name = proto.Field(proto.STRING, number=1) + + type_ = proto.Field(proto.ENUM, number=2, enum=Type,) + + definition = proto.Field(proto.STRING, number=3) + + +class CreateSchemaRequest(proto.Message): + r"""Request for the CreateSchema method. + + Attributes: + parent (str): + Required. The name of the project in which to create the + schema. Format is ``projects/{project-id}``. + schema (~.gp_schema.Schema): + Required. The schema object to create. + + This schema's ``name`` parameter is ignored. The schema + object returned by CreateSchema will have a ``name`` made + using the given ``parent`` and ``schema_id``. + schema_id (str): + The ID to use for the schema, which will become the final + component of the schema's resource name. + + See + https://cloud.google.com/pubsub/docs/admin#resource_names + for resource name constraints. + """ + + parent = proto.Field(proto.STRING, number=1) + + schema = proto.Field(proto.MESSAGE, number=2, message="Schema",) + + schema_id = proto.Field(proto.STRING, number=3) + + +class GetSchemaRequest(proto.Message): + r"""Request for the GetSchema method. + + Attributes: + name (str): + Required. The name of the schema to get. Format is + ``projects/{project}/schemas/{schema}``. + view (~.gp_schema.SchemaView): + The set of fields to return in the response. If not set, + returns a Schema with ``name`` and ``type``, but not + ``definition``. Set to ``FULL`` to retrieve all fields. + """ + + name = proto.Field(proto.STRING, number=1) + + view = proto.Field(proto.ENUM, number=2, enum="SchemaView",) + + +class ListSchemasRequest(proto.Message): + r"""Request for the ``ListSchemas`` method. + + Attributes: + parent (str): + Required. The name of the project in which to list schemas. + Format is ``projects/{project-id}``. + view (~.gp_schema.SchemaView): + The set of Schema fields to return in the response. If not + set, returns Schemas with ``name`` and ``type``, but not + ``definition``. Set to ``FULL`` to retrieve all fields. + page_size (int): + Maximum number of schemas to return. + page_token (str): + The value returned by the last ``ListSchemasResponse``; + indicates that this is a continuation of a prior + ``ListSchemas`` call, and that the system should return the + next page of data. + """ + + parent = proto.Field(proto.STRING, number=1) + + view = proto.Field(proto.ENUM, number=2, enum="SchemaView",) + + page_size = proto.Field(proto.INT32, number=3) + + page_token = proto.Field(proto.STRING, number=4) + + +class ListSchemasResponse(proto.Message): + r"""Response for the ``ListSchemas`` method. + + Attributes: + schemas (Sequence[~.gp_schema.Schema]): + The resulting schemas. + next_page_token (str): + If not empty, indicates that there may be more schemas that + match the request; this value should be passed in a new + ``ListSchemasRequest``. + """ + + @property + def raw_page(self): + return self + + schemas = proto.RepeatedField(proto.MESSAGE, number=1, message="Schema",) + + next_page_token = proto.Field(proto.STRING, number=2) + + +class DeleteSchemaRequest(proto.Message): + r"""Request for the ``DeleteSchema`` method. + + Attributes: + name (str): + Required. Name of the schema to delete. Format is + ``projects/{project}/schemas/{schema}``. + """ + + name = proto.Field(proto.STRING, number=1) + + +class ValidateSchemaRequest(proto.Message): + r"""Request for the ``ValidateSchema`` method. + + Attributes: + parent (str): + Required. The name of the project in which to validate + schemas. Format is ``projects/{project-id}``. + schema (~.gp_schema.Schema): + Required. The schema object to validate. + """ + + parent = proto.Field(proto.STRING, number=1) + + schema = proto.Field(proto.MESSAGE, number=2, message="Schema",) + + +class ValidateSchemaResponse(proto.Message): + r"""Response for the ``ValidateSchema`` method.""" + + +class ValidateMessageRequest(proto.Message): + r"""Request for the ``ValidateMessage`` method. + + Attributes: + parent (str): + Required. The name of the project in which to validate + schemas. Format is ``projects/{project-id}``. + name (str): + Name of the schema against which to validate. + + Format is ``projects/{project}/schemas/{schema}``. + schema (~.gp_schema.Schema): + Ad-hoc schema against which to validate + message (bytes): + Message to validate against the provided ``schema_spec``. + encoding (~.gp_schema.Encoding): + The encoding expected for messages + """ + + parent = proto.Field(proto.STRING, number=1) + + name = proto.Field(proto.STRING, number=2, oneof="schema_spec") + + schema = proto.Field( + proto.MESSAGE, number=3, oneof="schema_spec", message="Schema", + ) + + message = proto.Field(proto.BYTES, number=4) + + encoding = proto.Field(proto.ENUM, number=5, enum="Encoding",) + + +class ValidateMessageResponse(proto.Message): + r"""Response for the ``ValidateMessage`` method.""" + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/scripts/fixup_pubsub_v1_keywords.py b/scripts/fixup_pubsub_v1_keywords.py index c00db1ebc..071e3665f 100644 --- a/scripts/fixup_pubsub_v1_keywords.py +++ b/scripts/fixup_pubsub_v1_keywords.py @@ -42,16 +42,20 @@ class pubsubCallTransformer(cst.CSTTransformer): CTRL_PARAMS: Tuple[str] = ('retry', 'timeout', 'metadata') METHOD_TO_PARAMS: Dict[str, Tuple[str]] = { 'acknowledge': ('subscription', 'ack_ids', ), + 'create_schema': ('parent', 'schema', 'schema_id', ), 'create_snapshot': ('name', 'subscription', 'labels', ), 'create_subscription': ('name', 'topic', 'push_config', 'ack_deadline_seconds', 'retain_acked_messages', 'message_retention_duration', 'labels', 'enable_message_ordering', 'expiration_policy', 'filter', 'dead_letter_policy', 'retry_policy', 'detached', ), - 'create_topic': ('name', 'labels', 'message_storage_policy', 'kms_key_name', ), + 'create_topic': ('name', 'labels', 'message_storage_policy', 'kms_key_name', 'schema_settings', 'satisfies_pzs', ), + 'delete_schema': ('name', ), 'delete_snapshot': ('snapshot', ), 'delete_subscription': ('subscription', ), 'delete_topic': ('topic', ), 'detach_subscription': ('subscription', ), + 'get_schema': ('name', 'view', ), 'get_snapshot': ('snapshot', ), 'get_subscription': ('subscription', ), 'get_topic': ('topic', ), + 'list_schemas': ('parent', 'view', 'page_size', 'page_token', ), 'list_snapshots': ('project', 'page_size', 'page_token', ), 'list_subscriptions': ('project', 'page_size', 'page_token', ), 'list_topics': ('project', 'page_size', 'page_token', ), @@ -66,6 +70,8 @@ class pubsubCallTransformer(cst.CSTTransformer): 'update_snapshot': ('snapshot', 'update_mask', ), 'update_subscription': ('subscription', 'update_mask', ), 'update_topic': ('topic', 'update_mask', ), + 'validate_message': ('parent', 'name', 'schema', 'message', 'encoding', ), + 'validate_schema': ('parent', 'schema', ), 'get_iam_policy': ('resource', 'options', ), 'set_iam_policy': ('resource', 'policy', ), diff --git a/synth.metadata b/synth.metadata index 31f1bfd44..4718c28f5 100644 --- a/synth.metadata +++ b/synth.metadata @@ -3,37 +3,37 @@ { "git": { "name": ".", - "remote": "https://github.com/googleapis/python-pubsub.git", - "sha": "258414727827e56d2678ada28b9bbd88b5c6aa6f" + "remote": "git@github.com:googleapis/python-pubsub", + "sha": "aa45340d999f845c67396e8740e96f8a8caafd16" } }, { "git": { "name": "googleapis", "remote": "https://github.com/googleapis/googleapis.git", - "sha": "0509f4bc8c86495f50a92f536bddc625797eda63", - "internalRef": "347398860" + "sha": "57fc4a8a94a5bd015a83fb0f0a1707f62254b2cd", + "internalRef": "348813319" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "18c5dbdb4ac8cf75d4d8174e7b4558f48e76f8a1" + "sha": "6b026e1443948dcfc0b9e3289c85e940eb70f694" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "18c5dbdb4ac8cf75d4d8174e7b4558f48e76f8a1" + "sha": "6b026e1443948dcfc0b9e3289c85e940eb70f694" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "18c5dbdb4ac8cf75d4d8174e7b4558f48e76f8a1" + "sha": "6b026e1443948dcfc0b9e3289c85e940eb70f694" } } ], @@ -47,103 +47,5 @@ "generator": "bazel" } } - ], - "generatedFiles": [ - ".flake8", - ".github/CONTRIBUTING.md", - ".github/ISSUE_TEMPLATE/bug_report.md", - ".github/ISSUE_TEMPLATE/feature_request.md", - ".github/ISSUE_TEMPLATE/support_request.md", - ".github/PULL_REQUEST_TEMPLATE.md", - ".github/release-please.yml", - ".github/snippet-bot.yml", - ".gitignore", - ".kokoro/build.sh", - ".kokoro/continuous/common.cfg", - ".kokoro/continuous/continuous.cfg", - ".kokoro/docker/docs/Dockerfile", - ".kokoro/docker/docs/fetch_gpg_keys.sh", - ".kokoro/docs/common.cfg", - ".kokoro/docs/docs-presubmit.cfg", - ".kokoro/docs/docs.cfg", - ".kokoro/populate-secrets.sh", - ".kokoro/presubmit/common.cfg", - ".kokoro/presubmit/presubmit.cfg", - ".kokoro/publish-docs.sh", - ".kokoro/release.sh", - ".kokoro/release/common.cfg", - ".kokoro/release/release.cfg", - ".kokoro/samples/lint/common.cfg", - ".kokoro/samples/lint/continuous.cfg", - ".kokoro/samples/lint/periodic.cfg", - ".kokoro/samples/lint/presubmit.cfg", - ".kokoro/samples/python3.6/common.cfg", - ".kokoro/samples/python3.6/continuous.cfg", - ".kokoro/samples/python3.6/periodic.cfg", - ".kokoro/samples/python3.6/presubmit.cfg", - ".kokoro/samples/python3.7/common.cfg", - ".kokoro/samples/python3.7/continuous.cfg", - ".kokoro/samples/python3.7/periodic.cfg", - ".kokoro/samples/python3.7/presubmit.cfg", - ".kokoro/samples/python3.8/common.cfg", - ".kokoro/samples/python3.8/continuous.cfg", - ".kokoro/samples/python3.8/periodic.cfg", - ".kokoro/samples/python3.8/presubmit.cfg", - ".kokoro/test-samples.sh", - ".kokoro/trampoline.sh", - ".kokoro/trampoline_v2.sh", - ".pre-commit-config.yaml", - ".trampolinerc", - "CODE_OF_CONDUCT.md", - "CONTRIBUTING.rst", - "LICENSE", - "MANIFEST.in", - "docs/_static/custom.css", - "docs/_templates/layout.html", - "docs/conf.py", - "docs/multiprocessing.rst", - "google/cloud/pubsub_v1/proto/pubsub.proto", - "google/pubsub/__init__.py", - "google/pubsub/py.typed", - "google/pubsub_v1/__init__.py", - "google/pubsub_v1/py.typed", - "google/pubsub_v1/services/__init__.py", - "google/pubsub_v1/services/publisher/__init__.py", - "google/pubsub_v1/services/publisher/async_client.py", - "google/pubsub_v1/services/publisher/client.py", - "google/pubsub_v1/services/publisher/pagers.py", - "google/pubsub_v1/services/publisher/transports/__init__.py", - "google/pubsub_v1/services/publisher/transports/base.py", - "google/pubsub_v1/services/publisher/transports/grpc.py", - "google/pubsub_v1/services/publisher/transports/grpc_asyncio.py", - "google/pubsub_v1/services/subscriber/__init__.py", - "google/pubsub_v1/services/subscriber/async_client.py", - "google/pubsub_v1/services/subscriber/client.py", - "google/pubsub_v1/services/subscriber/pagers.py", - "google/pubsub_v1/services/subscriber/transports/__init__.py", - "google/pubsub_v1/services/subscriber/transports/base.py", - "google/pubsub_v1/services/subscriber/transports/grpc.py", - "google/pubsub_v1/services/subscriber/transports/grpc_asyncio.py", - "google/pubsub_v1/types/__init__.py", - "google/pubsub_v1/types/pubsub.py", - "mypy.ini", - "noxfile.py", - "renovate.json", - "samples/AUTHORING_GUIDE.md", - "samples/CONTRIBUTING.md", - "samples/snippets/noxfile.py", - "scripts/decrypt-secrets.sh", - "scripts/fixup_pubsub_v1_keywords.py", - "scripts/readme-gen/readme_gen.py", - "scripts/readme-gen/templates/README.tmpl.rst", - "scripts/readme-gen/templates/auth.tmpl.rst", - "scripts/readme-gen/templates/auth_api_key.tmpl.rst", - "scripts/readme-gen/templates/install_deps.tmpl.rst", - "scripts/readme-gen/templates/install_portaudio.tmpl.rst", - "setup.cfg", - "testing/.gitignore", - "tests/unit/gapic/pubsub_v1/__init__.py", - "tests/unit/gapic/pubsub_v1/test_publisher.py", - "tests/unit/gapic/pubsub_v1/test_subscriber.py" ] } \ No newline at end of file diff --git a/synth.py b/synth.py index fe1b0838e..fb42355e0 100644 --- a/synth.py +++ b/synth.py @@ -14,6 +14,7 @@ """This script is used to synthesize generated parts of this library.""" +import re import textwrap import synthtool as s @@ -63,6 +64,21 @@ \g<0>""", ) +# Modify GRPC options in transports. +count = s.replace( + ["google/pubsub_v1/services/*/transports/grpc*", "tests/unit/gapic/pubsub_v1/*"], + "options=\[.*?\]", + """options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ("grpc.keepalive_time_ms", 30000), + ]""", + flags=re.MULTILINE | re.DOTALL, +) + +if count < 18: + raise Exception("Expected replacements for gRPC channel options not made.") + # Monkey patch the streaming_pull() GAPIC method to disable pre-fetching stream # results. s.replace( @@ -83,11 +99,7 @@ # Docstrings of *_iam_policy() methods are formatted poorly and must be fixed # in order to avoid docstring format warnings in docs. -s.replace( - "google/pubsub_v1/services/*er/client.py", - r"(\s+)Args:", - "\n\g<1>Args:" -) +s.replace("google/pubsub_v1/services/*er/client.py", r"(\s+)Args:", "\n\g<1>Args:") s.replace( "google/pubsub_v1/services/*er/client.py", r"(\s+)\*\*JSON Example\*\*\s+::", diff --git a/tests/unit/gapic/pubsub_v1/test_publisher.py b/tests/unit/gapic/pubsub_v1/test_publisher.py index 79fc0c60c..d1c34d474 100644 --- a/tests/unit/gapic/pubsub_v1/test_publisher.py +++ b/tests/unit/gapic/pubsub_v1/test_publisher.py @@ -43,6 +43,7 @@ from google.pubsub_v1.services.publisher import pagers from google.pubsub_v1.services.publisher import transports from google.pubsub_v1.types import pubsub +from google.pubsub_v1.types import schema def client_cert_source_callback(): @@ -440,7 +441,7 @@ def test_create_topic(transport: str = "grpc", request_type=pubsub.Topic): with mock.patch.object(type(client.transport.create_topic), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = pubsub.Topic( - name="name_value", kms_key_name="kms_key_name_value", + name="name_value", kms_key_name="kms_key_name_value", satisfies_pzs=True, ) response = client.create_topic(request) @@ -459,6 +460,8 @@ def test_create_topic(transport: str = "grpc", request_type=pubsub.Topic): assert response.kms_key_name == "kms_key_name_value" + assert response.satisfies_pzs is True + def test_create_topic_from_dict(): test_create_topic(request_type=dict) @@ -480,7 +483,11 @@ async def test_create_topic_async( with mock.patch.object(type(client.transport.create_topic), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( - pubsub.Topic(name="name_value", kms_key_name="kms_key_name_value",) + pubsub.Topic( + name="name_value", + kms_key_name="kms_key_name_value", + satisfies_pzs=True, + ) ) response = await client.create_topic(request) @@ -498,6 +505,8 @@ async def test_create_topic_async( assert response.kms_key_name == "kms_key_name_value" + assert response.satisfies_pzs is True + @pytest.mark.asyncio async def test_create_topic_async_from_dict(): @@ -631,7 +640,7 @@ def test_update_topic(transport: str = "grpc", request_type=pubsub.UpdateTopicRe with mock.patch.object(type(client.transport.update_topic), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = pubsub.Topic( - name="name_value", kms_key_name="kms_key_name_value", + name="name_value", kms_key_name="kms_key_name_value", satisfies_pzs=True, ) response = client.update_topic(request) @@ -650,6 +659,8 @@ def test_update_topic(transport: str = "grpc", request_type=pubsub.UpdateTopicRe assert response.kms_key_name == "kms_key_name_value" + assert response.satisfies_pzs is True + def test_update_topic_from_dict(): test_update_topic(request_type=dict) @@ -671,7 +682,11 @@ async def test_update_topic_async( with mock.patch.object(type(client.transport.update_topic), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( - pubsub.Topic(name="name_value", kms_key_name="kms_key_name_value",) + pubsub.Topic( + name="name_value", + kms_key_name="kms_key_name_value", + satisfies_pzs=True, + ) ) response = await client.update_topic(request) @@ -689,6 +704,8 @@ async def test_update_topic_async( assert response.kms_key_name == "kms_key_name_value" + assert response.satisfies_pzs is True + @pytest.mark.asyncio async def test_update_topic_async_from_dict(): @@ -958,7 +975,7 @@ def test_get_topic(transport: str = "grpc", request_type=pubsub.GetTopicRequest) with mock.patch.object(type(client.transport.get_topic), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = pubsub.Topic( - name="name_value", kms_key_name="kms_key_name_value", + name="name_value", kms_key_name="kms_key_name_value", satisfies_pzs=True, ) response = client.get_topic(request) @@ -977,6 +994,8 @@ def test_get_topic(transport: str = "grpc", request_type=pubsub.GetTopicRequest) assert response.kms_key_name == "kms_key_name_value" + assert response.satisfies_pzs is True + def test_get_topic_from_dict(): test_get_topic(request_type=dict) @@ -998,7 +1017,11 @@ async def test_get_topic_async( with mock.patch.object(type(client.transport.get_topic), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( - pubsub.Topic(name="name_value", kms_key_name="kms_key_name_value",) + pubsub.Topic( + name="name_value", + kms_key_name="kms_key_name_value", + satisfies_pzs=True, + ) ) response = await client.get_topic(request) @@ -1016,6 +1039,8 @@ async def test_get_topic_async( assert response.kms_key_name == "kms_key_name_value" + assert response.satisfies_pzs is True + @pytest.mark.asyncio async def test_get_topic_async_from_dict(): @@ -2675,6 +2700,7 @@ def test_publisher_transport_channel_mtls_with_client_cert_source(transport_clas options=[ ("grpc.max_send_message_length", -1), ("grpc.max_receive_message_length", -1), + ("grpc.keepalive_time_ms", 30000), ], ) assert transport.grpc_channel == mock_grpc_channel @@ -2720,14 +2746,38 @@ def test_publisher_transport_channel_mtls_with_adc(transport_class): options=[ ("grpc.max_send_message_length", -1), ("grpc.max_receive_message_length", -1), + ("grpc.keepalive_time_ms", 30000), ], ) assert transport.grpc_channel == mock_grpc_channel -def test_subscription_path(): +def test_schema_path(): project = "squid" - subscription = "clam" + schema = "clam" + + expected = "projects/{project}/schemas/{schema}".format( + project=project, schema=schema, + ) + actual = PublisherClient.schema_path(project, schema) + assert expected == actual + + +def test_parse_schema_path(): + expected = { + "project": "whelk", + "schema": "octopus", + } + path = PublisherClient.schema_path(**expected) + + # Check that the path construction is reversible. + actual = PublisherClient.parse_schema_path(path) + assert expected == actual + + +def test_subscription_path(): + project = "oyster" + subscription = "nudibranch" expected = "projects/{project}/subscriptions/{subscription}".format( project=project, subscription=subscription, @@ -2738,8 +2788,8 @@ def test_subscription_path(): def test_parse_subscription_path(): expected = { - "project": "whelk", - "subscription": "octopus", + "project": "cuttlefish", + "subscription": "mussel", } path = PublisherClient.subscription_path(**expected) @@ -2749,8 +2799,8 @@ def test_parse_subscription_path(): def test_topic_path(): - project = "oyster" - topic = "nudibranch" + project = "winkle" + topic = "nautilus" expected = "projects/{project}/topics/{topic}".format(project=project, topic=topic,) actual = PublisherClient.topic_path(project, topic) @@ -2759,8 +2809,8 @@ def test_topic_path(): def test_parse_topic_path(): expected = { - "project": "cuttlefish", - "topic": "mussel", + "project": "scallop", + "topic": "abalone", } path = PublisherClient.topic_path(**expected) @@ -2770,7 +2820,7 @@ def test_parse_topic_path(): def test_common_billing_account_path(): - billing_account = "winkle" + billing_account = "squid" expected = "billingAccounts/{billing_account}".format( billing_account=billing_account, @@ -2781,7 +2831,7 @@ def test_common_billing_account_path(): def test_parse_common_billing_account_path(): expected = { - "billing_account": "nautilus", + "billing_account": "clam", } path = PublisherClient.common_billing_account_path(**expected) @@ -2791,7 +2841,7 @@ def test_parse_common_billing_account_path(): def test_common_folder_path(): - folder = "scallop" + folder = "whelk" expected = "folders/{folder}".format(folder=folder,) actual = PublisherClient.common_folder_path(folder) @@ -2800,7 +2850,7 @@ def test_common_folder_path(): def test_parse_common_folder_path(): expected = { - "folder": "abalone", + "folder": "octopus", } path = PublisherClient.common_folder_path(**expected) @@ -2810,7 +2860,7 @@ def test_parse_common_folder_path(): def test_common_organization_path(): - organization = "squid" + organization = "oyster" expected = "organizations/{organization}".format(organization=organization,) actual = PublisherClient.common_organization_path(organization) @@ -2819,7 +2869,7 @@ def test_common_organization_path(): def test_parse_common_organization_path(): expected = { - "organization": "clam", + "organization": "nudibranch", } path = PublisherClient.common_organization_path(**expected) @@ -2829,7 +2879,7 @@ def test_parse_common_organization_path(): def test_common_project_path(): - project = "whelk" + project = "cuttlefish" expected = "projects/{project}".format(project=project,) actual = PublisherClient.common_project_path(project) @@ -2838,7 +2888,7 @@ def test_common_project_path(): def test_parse_common_project_path(): expected = { - "project": "octopus", + "project": "mussel", } path = PublisherClient.common_project_path(**expected) @@ -2848,8 +2898,8 @@ def test_parse_common_project_path(): def test_common_location_path(): - project = "oyster" - location = "nudibranch" + project = "winkle" + location = "nautilus" expected = "projects/{project}/locations/{location}".format( project=project, location=location, @@ -2860,8 +2910,8 @@ def test_common_location_path(): def test_parse_common_location_path(): expected = { - "project": "cuttlefish", - "location": "mussel", + "project": "scallop", + "location": "abalone", } path = PublisherClient.common_location_path(**expected) diff --git a/tests/unit/gapic/pubsub_v1/test_schema_service.py b/tests/unit/gapic/pubsub_v1/test_schema_service.py new file mode 100644 index 000000000..79cc71324 --- /dev/null +++ b/tests/unit/gapic/pubsub_v1/test_schema_service.py @@ -0,0 +1,2587 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import os +import mock + +import grpc +from grpc.experimental import aio +import math +import pytest +from proto.marshal.rules.dates import DurationRule, TimestampRule + +from google import auth +from google.api_core import client_options +from google.api_core import exceptions +from google.api_core import gapic_v1 +from google.api_core import grpc_helpers +from google.api_core import grpc_helpers_async +from google.auth import credentials +from google.auth.exceptions import MutualTLSChannelError +from google.iam.v1 import iam_policy_pb2 as iam_policy # type: ignore +from google.iam.v1 import options_pb2 as options # type: ignore +from google.iam.v1 import policy_pb2 as policy # type: ignore +from google.oauth2 import service_account +from google.pubsub_v1.services.schema_service import SchemaServiceAsyncClient +from google.pubsub_v1.services.schema_service import SchemaServiceClient +from google.pubsub_v1.services.schema_service import pagers +from google.pubsub_v1.services.schema_service import transports +from google.pubsub_v1.types import schema +from google.pubsub_v1.types import schema as gp_schema + + +def client_cert_source_callback(): + return b"cert bytes", b"key bytes" + + +# If default endpoint is localhost, then default mtls endpoint will be the same. +# This method modifies the default endpoint so the client can produce a different +# mtls endpoint for endpoint testing purposes. +def modify_default_endpoint(client): + return ( + "foo.googleapis.com" + if ("localhost" in client.DEFAULT_ENDPOINT) + else client.DEFAULT_ENDPOINT + ) + + +def test__get_default_mtls_endpoint(): + api_endpoint = "example.googleapis.com" + api_mtls_endpoint = "example.mtls.googleapis.com" + sandbox_endpoint = "example.sandbox.googleapis.com" + sandbox_mtls_endpoint = "example.mtls.sandbox.googleapis.com" + non_googleapi = "api.example.com" + + assert SchemaServiceClient._get_default_mtls_endpoint(None) is None + assert ( + SchemaServiceClient._get_default_mtls_endpoint(api_endpoint) + == api_mtls_endpoint + ) + assert ( + SchemaServiceClient._get_default_mtls_endpoint(api_mtls_endpoint) + == api_mtls_endpoint + ) + assert ( + SchemaServiceClient._get_default_mtls_endpoint(sandbox_endpoint) + == sandbox_mtls_endpoint + ) + assert ( + SchemaServiceClient._get_default_mtls_endpoint(sandbox_mtls_endpoint) + == sandbox_mtls_endpoint + ) + assert ( + SchemaServiceClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi + ) + + +@pytest.mark.parametrize( + "client_class", [SchemaServiceClient, SchemaServiceAsyncClient] +) +def test_schema_service_client_from_service_account_file(client_class): + creds = credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_file" + ) as factory: + factory.return_value = creds + client = client_class.from_service_account_file("dummy/file/path.json") + assert client.transport._credentials == creds + + client = client_class.from_service_account_json("dummy/file/path.json") + assert client.transport._credentials == creds + + assert client.transport._host == "pubsub.googleapis.com:443" + + +def test_schema_service_client_get_transport_class(): + transport = SchemaServiceClient.get_transport_class() + assert transport == transports.SchemaServiceGrpcTransport + + transport = SchemaServiceClient.get_transport_class("grpc") + assert transport == transports.SchemaServiceGrpcTransport + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (SchemaServiceClient, transports.SchemaServiceGrpcTransport, "grpc"), + ( + SchemaServiceAsyncClient, + transports.SchemaServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +@mock.patch.object( + SchemaServiceClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(SchemaServiceClient), +) +@mock.patch.object( + SchemaServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(SchemaServiceAsyncClient), +) +def test_schema_service_client_client_options( + client_class, transport_class, transport_name +): + # Check that if channel is provided we won't create a new one. + with mock.patch.object(SchemaServiceClient, "get_transport_class") as gtc: + transport = transport_class(credentials=credentials.AnonymousCredentials()) + client = client_class(transport=transport) + gtc.assert_not_called() + + # Check that if channel is provided via str we will create a new one. + with mock.patch.object(SchemaServiceClient, "get_transport_class") as gtc: + client = client_class(transport=transport_name) + gtc.assert_called() + + # Check the case api_endpoint is provided. + options = client_options.ClientOptions(api_endpoint="squid.clam.whelk") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host="squid.clam.whelk", + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is + # "never". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is + # "always". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_MTLS_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError): + client = client_class() + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id="octopus", + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,use_client_cert_env", + [ + (SchemaServiceClient, transports.SchemaServiceGrpcTransport, "grpc", "true"), + ( + SchemaServiceAsyncClient, + transports.SchemaServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + (SchemaServiceClient, transports.SchemaServiceGrpcTransport, "grpc", "false"), + ( + SchemaServiceAsyncClient, + transports.SchemaServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ], +) +@mock.patch.object( + SchemaServiceClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(SchemaServiceClient), +) +@mock.patch.object( + SchemaServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(SchemaServiceAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_schema_service_client_mtls_env_auto( + client_class, transport_class, transport_name, use_client_cert_env +): + # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default + # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists. + + # Check the case client_cert_source is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + options = client_options.ClientOptions( + client_cert_source=client_cert_source_callback + ) + with mock.patch.object(transport_class, "__init__") as patched: + ssl_channel_creds = mock.Mock() + with mock.patch( + "grpc.ssl_channel_credentials", return_value=ssl_channel_creds + ): + patched.return_value = None + client = client_class(client_options=options) + + if use_client_cert_env == "false": + expected_ssl_channel_creds = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_ssl_channel_creds = ssl_channel_creds + expected_host = client.DEFAULT_MTLS_ENDPOINT + + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + ssl_channel_credentials=expected_ssl_channel_creds, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case ADC client cert is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + ): + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.ssl_credentials", + new_callable=mock.PropertyMock, + ) as ssl_credentials_mock: + if use_client_cert_env == "false": + is_mtls_mock.return_value = False + ssl_credentials_mock.return_value = None + expected_host = client.DEFAULT_ENDPOINT + expected_ssl_channel_creds = None + else: + is_mtls_mock.return_value = True + ssl_credentials_mock.return_value = mock.Mock() + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_ssl_channel_creds = ( + ssl_credentials_mock.return_value + ) + + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + ssl_channel_credentials=expected_ssl_channel_creds, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + ): + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + is_mtls_mock.return_value = False + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (SchemaServiceClient, transports.SchemaServiceGrpcTransport, "grpc"), + ( + SchemaServiceAsyncClient, + transports.SchemaServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +def test_schema_service_client_client_options_scopes( + client_class, transport_class, transport_name +): + # Check the case scopes are provided. + options = client_options.ClientOptions(scopes=["1", "2"],) + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=["1", "2"], + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (SchemaServiceClient, transports.SchemaServiceGrpcTransport, "grpc"), + ( + SchemaServiceAsyncClient, + transports.SchemaServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +def test_schema_service_client_client_options_credentials_file( + client_class, transport_class, transport_name +): + # Check the case credentials file is provided. + options = client_options.ClientOptions(credentials_file="credentials.json") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file="credentials.json", + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +def test_schema_service_client_client_options_from_dict(): + with mock.patch( + "google.pubsub_v1.services.schema_service.transports.SchemaServiceGrpcTransport.__init__" + ) as grpc_transport: + grpc_transport.return_value = None + client = SchemaServiceClient( + client_options={"api_endpoint": "squid.clam.whelk"} + ) + grpc_transport.assert_called_once_with( + credentials=None, + credentials_file=None, + host="squid.clam.whelk", + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +def test_create_schema( + transport: str = "grpc", request_type=gp_schema.CreateSchemaRequest +): + client = SchemaServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.create_schema), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = gp_schema.Schema( + name="name_value", + type_=gp_schema.Schema.Type.PROTOCOL_BUFFER, + definition="definition_value", + ) + + response = client.create_schema(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == gp_schema.CreateSchemaRequest() + + # 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" + + +def test_create_schema_from_dict(): + test_create_schema(request_type=dict) + + +@pytest.mark.asyncio +async def test_create_schema_async( + transport: str = "grpc_asyncio", request_type=gp_schema.CreateSchemaRequest +): + client = SchemaServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.create_schema), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + gp_schema.Schema( + name="name_value", + type_=gp_schema.Schema.Type.PROTOCOL_BUFFER, + definition="definition_value", + ) + ) + + response = await client.create_schema(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == gp_schema.CreateSchemaRequest() + + # 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" + + +@pytest.mark.asyncio +async def test_create_schema_async_from_dict(): + await test_create_schema_async(request_type=dict) + + +def test_create_schema_field_headers(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = gp_schema.CreateSchemaRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.create_schema), "__call__") as call: + call.return_value = gp_schema.Schema() + + client.create_schema(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_create_schema_field_headers_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = gp_schema.CreateSchemaRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.create_schema), "__call__") as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(gp_schema.Schema()) + + await client.create_schema(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +def test_create_schema_flattened(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.create_schema), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = gp_schema.Schema() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.create_schema( + parent="parent_value", + schema=gp_schema.Schema(name="name_value"), + schema_id="schema_id_value", + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].schema == gp_schema.Schema(name="name_value") + + assert args[0].schema_id == "schema_id_value" + + +def test_create_schema_flattened_error(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + + # 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.asyncio +async def test_create_schema_flattened_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.create_schema), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = gp_schema.Schema() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(gp_schema.Schema()) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.create_schema( + parent="parent_value", + schema=gp_schema.Schema(name="name_value"), + schema_id="schema_id_value", + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].schema == gp_schema.Schema(name="name_value") + + assert args[0].schema_id == "schema_id_value" + + +@pytest.mark.asyncio +async def test_create_schema_flattened_error_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.create_schema( + gp_schema.CreateSchemaRequest(), + parent="parent_value", + schema=gp_schema.Schema(name="name_value"), + schema_id="schema_id_value", + ) + + +def test_get_schema(transport: str = "grpc", request_type=schema.GetSchemaRequest): + client = SchemaServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_schema), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = schema.Schema( + name="name_value", + type_=schema.Schema.Type.PROTOCOL_BUFFER, + definition="definition_value", + ) + + response = client.get_schema(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == schema.GetSchemaRequest() + + # 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" + + +def test_get_schema_from_dict(): + test_get_schema(request_type=dict) + + +@pytest.mark.asyncio +async def test_get_schema_async( + transport: str = "grpc_asyncio", request_type=schema.GetSchemaRequest +): + client = SchemaServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_schema), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + schema.Schema( + name="name_value", + type_=schema.Schema.Type.PROTOCOL_BUFFER, + definition="definition_value", + ) + ) + + response = await client.get_schema(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == schema.GetSchemaRequest() + + # 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" + + +@pytest.mark.asyncio +async def test_get_schema_async_from_dict(): + await test_get_schema_async(request_type=dict) + + +def test_get_schema_field_headers(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = schema.GetSchemaRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_schema), "__call__") as call: + call.return_value = schema.Schema() + + client.get_schema(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_get_schema_field_headers_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = schema.GetSchemaRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_schema), "__call__") as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(schema.Schema()) + + await client.get_schema(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value",) in kw["metadata"] + + +def test_get_schema_flattened(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_schema), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = schema.Schema() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.get_schema(name="name_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].name == "name_value" + + +def test_get_schema_flattened_error(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + + # 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", + ) + + +@pytest.mark.asyncio +async def test_get_schema_flattened_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_schema), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = schema.Schema() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(schema.Schema()) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.get_schema(name="name_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].name == "name_value" + + +@pytest.mark.asyncio +async def test_get_schema_flattened_error_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.get_schema( + schema.GetSchemaRequest(), name="name_value", + ) + + +def test_list_schemas(transport: str = "grpc", request_type=schema.ListSchemasRequest): + client = SchemaServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_schemas), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = schema.ListSchemasResponse( + next_page_token="next_page_token_value", + ) + + response = client.list_schemas(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == schema.ListSchemasRequest() + + # 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_from_dict(): + test_list_schemas(request_type=dict) + + +@pytest.mark.asyncio +async def test_list_schemas_async( + transport: str = "grpc_asyncio", request_type=schema.ListSchemasRequest +): + client = SchemaServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_schemas), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + schema.ListSchemasResponse(next_page_token="next_page_token_value",) + ) + + response = await client.list_schemas(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == schema.ListSchemasRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListSchemasAsyncPager) + + assert response.next_page_token == "next_page_token_value" + + +@pytest.mark.asyncio +async def test_list_schemas_async_from_dict(): + await test_list_schemas_async(request_type=dict) + + +def test_list_schemas_field_headers(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = schema.ListSchemasRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_schemas), "__call__") as call: + call.return_value = schema.ListSchemasResponse() + + client.list_schemas(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_list_schemas_field_headers_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = schema.ListSchemasRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_schemas), "__call__") as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + schema.ListSchemasResponse() + ) + + await client.list_schemas(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +def test_list_schemas_flattened(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_schemas), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = schema.ListSchemasResponse() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.list_schemas(parent="parent_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + +def test_list_schemas_flattened_error(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + + # 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", + ) + + +@pytest.mark.asyncio +async def test_list_schemas_flattened_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_schemas), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = schema.ListSchemasResponse() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + schema.ListSchemasResponse() + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.list_schemas(parent="parent_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + +@pytest.mark.asyncio +async def test_list_schemas_flattened_error_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.list_schemas( + schema.ListSchemasRequest(), parent="parent_value", + ) + + +def test_list_schemas_pager(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials,) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_schemas), "__call__") as call: + # Set the response to a series of pages. + call.side_effect = ( + 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(),],), + RuntimeError, + ) + + metadata = () + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", ""),)), + ) + pager = client.list_schemas(request={}) + + assert pager._metadata == metadata + + results = [i for i in pager] + assert len(results) == 6 + assert all(isinstance(i, schema.Schema) for i in results) + + +def test_list_schemas_pages(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials,) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_schemas), "__call__") as call: + # Set the response to a series of pages. + call.side_effect = ( + 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(),],), + RuntimeError, + ) + pages = list(client.list_schemas(request={}).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.asyncio +async def test_list_schemas_async_pager(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials,) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_schemas), "__call__", new_callable=mock.AsyncMock + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + 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(),],), + RuntimeError, + ) + async_pager = await client.list_schemas(request={},) + assert async_pager.next_page_token == "abc" + responses = [] + async for response in async_pager: + responses.append(response) + + assert len(responses) == 6 + assert all(isinstance(i, schema.Schema) for i in responses) + + +@pytest.mark.asyncio +async def test_list_schemas_async_pages(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials,) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_schemas), "__call__", new_callable=mock.AsyncMock + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + 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(),],), + RuntimeError, + ) + pages = [] + async for page_ in (await client.list_schemas(request={})).pages: + pages.append(page_) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +def test_delete_schema( + transport: str = "grpc", request_type=schema.DeleteSchemaRequest +): + client = SchemaServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.delete_schema), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = None + + response = client.delete_schema(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == schema.DeleteSchemaRequest() + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_schema_from_dict(): + test_delete_schema(request_type=dict) + + +@pytest.mark.asyncio +async def test_delete_schema_async( + transport: str = "grpc_asyncio", request_type=schema.DeleteSchemaRequest +): + client = SchemaServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.delete_schema), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) + + response = await client.delete_schema(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == schema.DeleteSchemaRequest() + + # Establish that the response is the type that we expect. + assert response is None + + +@pytest.mark.asyncio +async def test_delete_schema_async_from_dict(): + await test_delete_schema_async(request_type=dict) + + +def test_delete_schema_field_headers(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = schema.DeleteSchemaRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.delete_schema), "__call__") as call: + call.return_value = None + + client.delete_schema(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_delete_schema_field_headers_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = schema.DeleteSchemaRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.delete_schema), "__call__") as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) + + await client.delete_schema(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value",) in kw["metadata"] + + +def test_delete_schema_flattened(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.delete_schema), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = None + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.delete_schema(name="name_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].name == "name_value" + + +def test_delete_schema_flattened_error(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + + # 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", + ) + + +@pytest.mark.asyncio +async def test_delete_schema_flattened_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.delete_schema), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = None + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.delete_schema(name="name_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].name == "name_value" + + +@pytest.mark.asyncio +async def test_delete_schema_flattened_error_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.delete_schema( + schema.DeleteSchemaRequest(), name="name_value", + ) + + +def test_validate_schema( + transport: str = "grpc", request_type=gp_schema.ValidateSchemaRequest +): + client = SchemaServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.validate_schema), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = gp_schema.ValidateSchemaResponse() + + response = client.validate_schema(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == gp_schema.ValidateSchemaRequest() + + # Establish that the response is the type that we expect. + + assert isinstance(response, gp_schema.ValidateSchemaResponse) + + +def test_validate_schema_from_dict(): + test_validate_schema(request_type=dict) + + +@pytest.mark.asyncio +async def test_validate_schema_async( + transport: str = "grpc_asyncio", request_type=gp_schema.ValidateSchemaRequest +): + client = SchemaServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.validate_schema), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + gp_schema.ValidateSchemaResponse() + ) + + response = await client.validate_schema(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == gp_schema.ValidateSchemaRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, gp_schema.ValidateSchemaResponse) + + +@pytest.mark.asyncio +async def test_validate_schema_async_from_dict(): + await test_validate_schema_async(request_type=dict) + + +def test_validate_schema_field_headers(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = gp_schema.ValidateSchemaRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.validate_schema), "__call__") as call: + call.return_value = gp_schema.ValidateSchemaResponse() + + client.validate_schema(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_validate_schema_field_headers_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = gp_schema.ValidateSchemaRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.validate_schema), "__call__") as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + gp_schema.ValidateSchemaResponse() + ) + + await client.validate_schema(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +def test_validate_schema_flattened(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.validate_schema), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = gp_schema.ValidateSchemaResponse() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.validate_schema( + parent="parent_value", schema=gp_schema.Schema(name="name_value"), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].schema == gp_schema.Schema(name="name_value") + + +def test_validate_schema_flattened_error(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + + # 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"), + ) + + +@pytest.mark.asyncio +async def test_validate_schema_flattened_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.validate_schema), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = gp_schema.ValidateSchemaResponse() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + gp_schema.ValidateSchemaResponse() + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.validate_schema( + parent="parent_value", schema=gp_schema.Schema(name="name_value"), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].schema == gp_schema.Schema(name="name_value") + + +@pytest.mark.asyncio +async def test_validate_schema_flattened_error_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.validate_schema( + gp_schema.ValidateSchemaRequest(), + parent="parent_value", + schema=gp_schema.Schema(name="name_value"), + ) + + +def test_validate_message( + transport: str = "grpc", request_type=schema.ValidateMessageRequest +): + client = SchemaServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.validate_message), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = schema.ValidateMessageResponse() + + response = client.validate_message(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == schema.ValidateMessageRequest() + + # Establish that the response is the type that we expect. + + assert isinstance(response, schema.ValidateMessageResponse) + + +def test_validate_message_from_dict(): + test_validate_message(request_type=dict) + + +@pytest.mark.asyncio +async def test_validate_message_async( + transport: str = "grpc_asyncio", request_type=schema.ValidateMessageRequest +): + client = SchemaServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.validate_message), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + schema.ValidateMessageResponse() + ) + + response = await client.validate_message(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == schema.ValidateMessageRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, schema.ValidateMessageResponse) + + +@pytest.mark.asyncio +async def test_validate_message_async_from_dict(): + await test_validate_message_async(request_type=dict) + + +def test_validate_message_field_headers(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = schema.ValidateMessageRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.validate_message), "__call__") as call: + call.return_value = schema.ValidateMessageResponse() + + client.validate_message(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_validate_message_field_headers_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = schema.ValidateMessageRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.validate_message), "__call__") as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + schema.ValidateMessageResponse() + ) + + await client.validate_message(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.SchemaServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SchemaServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.SchemaServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = SchemaServiceClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.SchemaServiceGrpcTransport( + credentials=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=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=credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.SchemaServiceGrpcAsyncIOTransport( + credentials=credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@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(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = SchemaServiceClient(credentials=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(exceptions.DuplicateCredentialArgs): + transport = transports.SchemaServiceTransport( + credentials=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=credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "create_schema", + "get_schema", + "list_schemas", + "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()) + + +def test_schema_service_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + auth, "load_credentials_from_file" + ) 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 = (credentials.AnonymousCredentials(), None) + transport = transports.SchemaServiceTransport( + credentials_file="credentials.json", quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + 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(auth, "default") as adc, mock.patch( + "google.pubsub_v1.services.schema_service.transports.SchemaServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.SchemaServiceTransport() + adc.assert_called_once() + + +def test_schema_service_auth_adc(): + # If no credentials are provided, we should use ADC credentials. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + SchemaServiceClient() + adc.assert_called_once_with( + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/pubsub", + ), + quota_project_id=None, + ) + + +def test_schema_service_transport_auth_adc(): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transports.SchemaServiceGrpcTransport( + host="squid.clam.whelk", quota_project_id="octopus" + ) + adc.assert_called_once_with( + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/pubsub", + ), + quota_project_id="octopus", + ) + + +def test_schema_service_host_no_port(): + client = SchemaServiceClient( + credentials=credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions( + api_endpoint="pubsub.googleapis.com" + ), + ) + assert client.transport._host == "pubsub.googleapis.com:443" + + +def test_schema_service_host_with_port(): + client = SchemaServiceClient( + credentials=credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions( + api_endpoint="pubsub.googleapis.com:8000" + ), + ) + assert client.transport._host == "pubsub.googleapis.com:8000" + + +def test_schema_service_grpc_transport_channel(): + channel = grpc.insecure_channel("http://localhost/") + + # Check that channel is used if provided. + transport = transports.SchemaServiceGrpcTransport( + host="squid.clam.whelk", channel=channel, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + assert transport._ssl_channel_credentials == None + + +def test_schema_service_grpc_asyncio_transport_channel(): + channel = aio.insecure_channel("http://localhost/") + + # Check that channel is used if provided. + transport = transports.SchemaServiceGrpcAsyncIOTransport( + host="squid.clam.whelk", channel=channel, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + assert transport._ssl_channel_credentials == None + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.SchemaServiceGrpcTransport, + transports.SchemaServiceGrpcAsyncIOTransport, + ], +) +def test_schema_service_transport_channel_mtls_with_client_cert_source(transport_class): + with mock.patch( + "grpc.ssl_channel_credentials", autospec=True + ) as grpc_ssl_channel_cred: + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + cred = credentials.AnonymousCredentials() + with pytest.warns(DeprecationWarning): + with mock.patch.object(auth, "default") as adc: + adc.return_value = (cred, None) + transport = transport_class( + host="squid.clam.whelk", + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + adc.assert_called_once() + + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/pubsub", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ("grpc.keepalive_time_ms", 30000), + ], + ) + assert transport.grpc_channel == mock_grpc_channel + assert transport._ssl_channel_credentials == mock_ssl_cred + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.SchemaServiceGrpcTransport, + transports.SchemaServiceGrpcAsyncIOTransport, + ], +) +def test_schema_service_transport_channel_mtls_with_adc(transport_class): + mock_ssl_cred = mock.Mock() + with mock.patch.multiple( + "google.auth.transport.grpc.SslCredentials", + __init__=mock.Mock(return_value=None), + ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), + ): + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + mock_cred = mock.Mock() + + with pytest.warns(DeprecationWarning): + transport = transport_class( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=None, + ) + + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/pubsub", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ("grpc.keepalive_time_ms", 30000), + ], + ) + assert transport.grpc_channel == mock_grpc_channel + + +def test_schema_path(): + project = "squid" + schema = "clam" + + expected = "projects/{project}/schemas/{schema}".format( + project=project, schema=schema, + ) + actual = SchemaServiceClient.schema_path(project, schema) + assert expected == actual + + +def test_parse_schema_path(): + expected = { + "project": "whelk", + "schema": "octopus", + } + path = SchemaServiceClient.schema_path(**expected) + + # Check that the path construction is reversible. + actual = SchemaServiceClient.parse_schema_path(path) + assert expected == actual + + +def test_common_billing_account_path(): + billing_account = "oyster" + + expected = "billingAccounts/{billing_account}".format( + billing_account=billing_account, + ) + actual = SchemaServiceClient.common_billing_account_path(billing_account) + assert expected == actual + + +def test_parse_common_billing_account_path(): + expected = { + "billing_account": "nudibranch", + } + path = SchemaServiceClient.common_billing_account_path(**expected) + + # Check that the path construction is reversible. + actual = SchemaServiceClient.parse_common_billing_account_path(path) + assert expected == actual + + +def test_common_folder_path(): + folder = "cuttlefish" + + expected = "folders/{folder}".format(folder=folder,) + actual = SchemaServiceClient.common_folder_path(folder) + assert expected == actual + + +def test_parse_common_folder_path(): + expected = { + "folder": "mussel", + } + path = SchemaServiceClient.common_folder_path(**expected) + + # Check that the path construction is reversible. + actual = SchemaServiceClient.parse_common_folder_path(path) + assert expected == actual + + +def test_common_organization_path(): + organization = "winkle" + + expected = "organizations/{organization}".format(organization=organization,) + actual = SchemaServiceClient.common_organization_path(organization) + assert expected == actual + + +def test_parse_common_organization_path(): + expected = { + "organization": "nautilus", + } + path = SchemaServiceClient.common_organization_path(**expected) + + # Check that the path construction is reversible. + actual = SchemaServiceClient.parse_common_organization_path(path) + assert expected == actual + + +def test_common_project_path(): + project = "scallop" + + expected = "projects/{project}".format(project=project,) + actual = SchemaServiceClient.common_project_path(project) + assert expected == actual + + +def test_parse_common_project_path(): + expected = { + "project": "abalone", + } + path = SchemaServiceClient.common_project_path(**expected) + + # Check that the path construction is reversible. + actual = SchemaServiceClient.parse_common_project_path(path) + assert expected == actual + + +def test_common_location_path(): + project = "squid" + location = "clam" + + expected = "projects/{project}/locations/{location}".format( + project=project, location=location, + ) + actual = SchemaServiceClient.common_location_path(project, location) + assert expected == actual + + +def test_parse_common_location_path(): + expected = { + "project": "whelk", + "location": "octopus", + } + path = SchemaServiceClient.common_location_path(**expected) + + # Check that the path construction is reversible. + actual = SchemaServiceClient.parse_common_location_path(path) + assert expected == actual + + +def test_client_withDEFAULT_CLIENT_INFO(): + client_info = gapic_v1.client_info.ClientInfo() + + with mock.patch.object( + transports.SchemaServiceTransport, "_prep_wrapped_messages" + ) as prep: + client = SchemaServiceClient( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + with mock.patch.object( + transports.SchemaServiceTransport, "_prep_wrapped_messages" + ) as prep: + transport_class = SchemaServiceClient.get_transport_class() + transport = transport_class( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + +def test_set_iam_policy(transport: str = "grpc"): + client = SchemaServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = iam_policy.SetIamPolicyRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.set_iam_policy), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = policy.Policy(version=774, etag=b"etag_blob",) + + response = client.set_iam_policy(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, policy.Policy) + + assert response.version == 774 + + assert response.etag == b"etag_blob" + + +@pytest.mark.asyncio +async def test_set_iam_policy_async(transport: str = "grpc_asyncio"): + client = SchemaServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = iam_policy.SetIamPolicyRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.set_iam_policy), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + policy.Policy(version=774, etag=b"etag_blob",) + ) + + response = await client.set_iam_policy(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, policy.Policy) + + assert response.version == 774 + + assert response.etag == b"etag_blob" + + +def test_set_iam_policy_field_headers(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = iam_policy.SetIamPolicyRequest() + request.resource = "resource/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.set_iam_policy), "__call__") as call: + call.return_value = policy.Policy() + + client.set_iam_policy(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "resource=resource/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_set_iam_policy_field_headers_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = iam_policy.SetIamPolicyRequest() + request.resource = "resource/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.set_iam_policy), "__call__") as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(policy.Policy()) + + await client.set_iam_policy(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "resource=resource/value",) in kw["metadata"] + + +def test_set_iam_policy_from_dict(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.set_iam_policy), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = policy.Policy() + + response = client.set_iam_policy( + request={ + "resource": "resource_value", + "policy": policy.Policy(version=774), + } + ) + call.assert_called() + + +@pytest.mark.asyncio +async def test_set_iam_policy_from_dict_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.set_iam_policy), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(policy.Policy()) + + response = await client.set_iam_policy( + request={ + "resource": "resource_value", + "policy": policy.Policy(version=774), + } + ) + call.assert_called() + + +def test_get_iam_policy(transport: str = "grpc"): + client = SchemaServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = iam_policy.GetIamPolicyRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_iam_policy), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = policy.Policy(version=774, etag=b"etag_blob",) + + response = client.get_iam_policy(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, policy.Policy) + + assert response.version == 774 + + assert response.etag == b"etag_blob" + + +@pytest.mark.asyncio +async def test_get_iam_policy_async(transport: str = "grpc_asyncio"): + client = SchemaServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = iam_policy.GetIamPolicyRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_iam_policy), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + policy.Policy(version=774, etag=b"etag_blob",) + ) + + response = await client.get_iam_policy(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, policy.Policy) + + assert response.version == 774 + + assert response.etag == b"etag_blob" + + +def test_get_iam_policy_field_headers(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = iam_policy.GetIamPolicyRequest() + request.resource = "resource/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_iam_policy), "__call__") as call: + call.return_value = policy.Policy() + + client.get_iam_policy(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "resource=resource/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_get_iam_policy_field_headers_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = iam_policy.GetIamPolicyRequest() + request.resource = "resource/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_iam_policy), "__call__") as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(policy.Policy()) + + await client.get_iam_policy(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "resource=resource/value",) in kw["metadata"] + + +def test_get_iam_policy_from_dict(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_iam_policy), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = policy.Policy() + + response = client.get_iam_policy( + request={ + "resource": "resource_value", + "options": options.GetPolicyOptions(requested_policy_version=2598), + } + ) + call.assert_called() + + +@pytest.mark.asyncio +async def test_get_iam_policy_from_dict_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_iam_policy), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(policy.Policy()) + + response = await client.get_iam_policy( + request={ + "resource": "resource_value", + "options": options.GetPolicyOptions(requested_policy_version=2598), + } + ) + call.assert_called() + + +def test_test_iam_permissions(transport: str = "grpc"): + client = SchemaServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = iam_policy.TestIamPermissionsRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.test_iam_permissions), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = iam_policy.TestIamPermissionsResponse( + permissions=["permissions_value"], + ) + + response = client.test_iam_permissions(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, iam_policy.TestIamPermissionsResponse) + + assert response.permissions == ["permissions_value"] + + +@pytest.mark.asyncio +async def test_test_iam_permissions_async(transport: str = "grpc_asyncio"): + client = SchemaServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = iam_policy.TestIamPermissionsRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.test_iam_permissions), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + iam_policy.TestIamPermissionsResponse(permissions=["permissions_value"],) + ) + + response = await client.test_iam_permissions(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, iam_policy.TestIamPermissionsResponse) + + assert response.permissions == ["permissions_value"] + + +def test_test_iam_permissions_field_headers(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = iam_policy.TestIamPermissionsRequest() + request.resource = "resource/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.test_iam_permissions), "__call__" + ) as call: + call.return_value = iam_policy.TestIamPermissionsResponse() + + client.test_iam_permissions(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "resource=resource/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_test_iam_permissions_field_headers_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = iam_policy.TestIamPermissionsRequest() + request.resource = "resource/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.test_iam_permissions), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + iam_policy.TestIamPermissionsResponse() + ) + + await client.test_iam_permissions(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "resource=resource/value",) in kw["metadata"] + + +def test_test_iam_permissions_from_dict(): + client = SchemaServiceClient(credentials=credentials.AnonymousCredentials(),) + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.test_iam_permissions), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = iam_policy.TestIamPermissionsResponse() + + response = client.test_iam_permissions( + request={ + "resource": "resource_value", + "permissions": ["permissions_value"], + } + ) + call.assert_called() + + +@pytest.mark.asyncio +async def test_test_iam_permissions_from_dict_async(): + client = SchemaServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.test_iam_permissions), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + iam_policy.TestIamPermissionsResponse() + ) + + response = await client.test_iam_permissions( + request={ + "resource": "resource_value", + "permissions": ["permissions_value"], + } + ) + call.assert_called() diff --git a/tests/unit/gapic/pubsub_v1/test_subscriber.py b/tests/unit/gapic/pubsub_v1/test_subscriber.py index 184b54026..8dec1c27d 100644 --- a/tests/unit/gapic/pubsub_v1/test_subscriber.py +++ b/tests/unit/gapic/pubsub_v1/test_subscriber.py @@ -3969,6 +3969,7 @@ def test_subscriber_transport_channel_mtls_with_client_cert_source(transport_cla options=[ ("grpc.max_send_message_length", -1), ("grpc.max_receive_message_length", -1), + ("grpc.keepalive_time_ms", 30000), ], ) assert transport.grpc_channel == mock_grpc_channel @@ -4014,6 +4015,7 @@ def test_subscriber_transport_channel_mtls_with_adc(transport_class): options=[ ("grpc.max_send_message_length", -1), ("grpc.max_receive_message_length", -1), + ("grpc.keepalive_time_ms", 30000), ], ) assert transport.grpc_channel == mock_grpc_channel diff --git a/tests/unit/pubsub_v1/publisher/test_publisher_client.py b/tests/unit/pubsub_v1/publisher/test_publisher_client.py index 0f661c2fa..5a95a23e2 100644 --- a/tests/unit/pubsub_v1/publisher/test_publisher_client.py +++ b/tests/unit/pubsub_v1/publisher/test_publisher_client.py @@ -130,7 +130,7 @@ def init(self, *args, **kwargs): def test_init_emulator(monkeypatch): - monkeypatch.setenv("PUBSUB_EMULATOR_HOST", "/foo/bar/") + monkeypatch.setenv("PUBSUB_EMULATOR_HOST", "/foo/bar:123") # NOTE: When the emulator host is set, a custom channel will be used, so # no credentials (mock ot otherwise) can be passed in. client = publisher.Client() @@ -140,7 +140,7 @@ def test_init_emulator(monkeypatch): # Sadly, there seems to be no good way to do this without poking at # the private API of gRPC. channel = client.api._transport.publish._channel - assert channel.target().decode("utf8") == "/foo/bar/" + assert channel.target().decode("utf8") == "/foo/bar:123" def test_message_ordering_enabled(): diff --git a/tests/unit/pubsub_v1/subscriber/test_subscriber_client.py b/tests/unit/pubsub_v1/subscriber/test_subscriber_client.py index d56289276..3262600e7 100644 --- a/tests/unit/pubsub_v1/subscriber/test_subscriber_client.py +++ b/tests/unit/pubsub_v1/subscriber/test_subscriber_client.py @@ -91,7 +91,7 @@ def init(self, *args, **kwargs): def test_init_emulator(monkeypatch): - monkeypatch.setenv("PUBSUB_EMULATOR_HOST", "/baz/bacon/") + monkeypatch.setenv("PUBSUB_EMULATOR_HOST", "/baz/bacon:123") # NOTE: When the emulator host is set, a custom channel will be used, so # no credentials (mock ot otherwise) can be passed in. client = subscriber.Client() @@ -101,7 +101,7 @@ def test_init_emulator(monkeypatch): # Sadly, there seems to be no good way to do this without poking at # the private API of gRPC. channel = client.api._transport.pull._channel - assert channel.target().decode("utf8") == "/baz/bacon/" + assert channel.target().decode("utf8") == "/baz/bacon:123" def test_class_method_factory():