Source code for google.cloud.pubsub_v1.publisher.client

# Copyright 2019, Google LLC All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import absolute_import

import copy
import os
import pkg_resources

import grpc
import six

from google.api_core import grpc_helpers
from google.oauth2 import service_account

from google.cloud.pubsub_v1 import _gapic
from google.cloud.pubsub_v1 import types
from google.cloud.pubsub_v1.gapic import publisher_client
from google.cloud.pubsub_v1.gapic.transports import publisher_grpc_transport
from google.cloud.pubsub_v1.publisher._batch import thread


__version__ = pkg_resources.get_distribution("google-cloud-pubsub").version

_BLACKLISTED_METHODS = (
    "publish",
    "from_service_account_file",
    "from_service_account_json",
)


[docs]@_gapic.add_methods(publisher_client.PublisherClient, blacklist=_BLACKLISTED_METHODS) class Client(object): """A publisher client for Google Cloud Pub/Sub. This creates an object that is capable of publishing messages. Generally, you can instantiate this client with no arguments, and you get sensible defaults. Args: batch_settings (~google.cloud.pubsub_v1.types.BatchSettings): The settings for batch publishing. kwargs (dict): Any additional arguments provided are sent as keyword arguments to the underlying :class:`~google.cloud.pubsub_v1.gapic.publisher_client.PublisherClient`. Generally you should not need to set additional keyword arguments. Optionally, publish retry settings can be set via ``client_config`` where user-provided retry configurations are applied to default retry settings. And regional endpoints can be set via ``client_options`` that takes a single key-value pair that defines the endpoint. Example: .. code-block:: python from google.cloud import pubsub_v1 publisher_client = pubsub_v1.PublisherClient( # Optional batch_settings = pubsub_v1.types.BatchSettings( max_bytes=1024, # One kilobyte max_latency=1, # One second ), # Optional client_config = { "interfaces": { "google.pubsub.v1.Publisher": { "retry_params": { "messaging": { 'total_timeout_millis': 650000, # default: 600000 } } } } }, # Optional client_options = { "api_endpoint": REGIONAL_ENDPOINT } ) """ _batch_class = thread.Batch def __init__(self, batch_settings=(), **kwargs): # Sanity check: Is our goal to use the emulator? # 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") ) # 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, 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 # Add the metrics headers, and instantiate the underlying GAPIC # client. self.api = publisher_client.PublisherClient(**kwargs) self.batch_settings = types.BatchSettings(*batch_settings) # The batches on the publisher client are responsible for holding # messages. One batch exists for each topic. self._batch_lock = self._batch_class.make_lock() self._batches = {}
[docs] @classmethod def from_service_account_file(cls, filename, batch_settings=(), **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. batch_settings (~google.cloud.pubsub_v1.types.BatchSettings): The settings for batch publishing. kwargs: Additional arguments to pass to the constructor. Returns: A Publisher :class:`~google.cloud.pubsub_v1.publisher.client.Client` instance that is the constructed client. """ credentials = service_account.Credentials.from_service_account_file(filename) kwargs["credentials"] = credentials return cls(batch_settings, **kwargs)
from_service_account_json = from_service_account_file @property def target(self): """Return the target (where the API is). Returns: str: The location of the API. """ return publisher_client.PublisherClient.SERVICE_ADDRESS def _batch(self, topic, create=False, autocommit=True): """Return the current batch for the provided topic. This will create a new batch if ``create=True`` or if no batch currently exists. Args: topic (str): A string representing the topic. create (bool): Whether to create a new batch. Defaults to :data:`False`. If :data:`True`, this will create a new batch even if one already exists. autocommit (bool): Whether to autocommit this batch. This is primarily useful for debugging and testing, since it allows the caller to avoid some side effects that batch creation might have (e.g. spawning a worker to publish a batch). Returns: ~.pubsub_v1._batch.Batch: The batch object. """ # If there is no matching batch yet, then potentially create one # and place it on the batches dictionary. with self._batch_lock: if not create: batch = self._batches.get(topic) if batch is None: create = True if create: batch = self._batch_class( autocommit=autocommit, client=self, settings=self.batch_settings, topic=topic, ) self._batches[topic] = batch return batch
[docs] def publish(self, topic, data, **attrs): """Publish a single message. .. note:: Messages in Pub/Sub are blobs of bytes. They are *binary* data, not text. You must send data as a bytestring (``bytes`` in Python 3; ``str`` in Python 2), and this library will raise an exception if you send a text string. The reason that this is so important (and why we do not try to coerce for you) is because Pub/Sub is also platform independent and there is no way to know how to decode messages properly on the other side; therefore, encoding and decoding is a required exercise for the developer. Add the given message to this object; this will cause it to be published once the batch either has enough messages or a sufficient period of time has elapsed. Example: >>> from google.cloud import pubsub_v1 >>> client = pubsub_v1.PublisherClient() >>> topic = client.topic_path('[PROJECT]', '[TOPIC]') >>> data = b'The rain in Wales falls mainly on the snails.' >>> response = client.publish(topic, data, username='guido') Args: topic (str): The topic to publish messages to. data (bytes): A bytestring representing the message body. This must be a bytestring. attrs (Mapping[str, str]): A dictionary of attributes to be sent as metadata. (These may be text strings or byte strings.) Returns: A :class:`~google.cloud.pubsub_v1.publisher.futures.Future` instance that conforms to Python Standard library's :class:`~concurrent.futures.Future` interface (but not an instance of that class). """ # Sanity check: Is the data being sent as a bytestring? # If it is literally anything else, complain loudly about it. if not isinstance(data, six.binary_type): raise TypeError( "Data being published to Pub/Sub must be sent " "as a bytestring." ) # Coerce all attributes to text strings. for k, v in copy.copy(attrs).items(): if isinstance(v, six.text_type): continue if isinstance(v, six.binary_type): attrs[k] = v.decode("utf-8") continue raise TypeError( "All attributes being published to Pub/Sub must " "be sent as text strings." ) # Create the Pub/Sub message object. message = types.PubsubMessage(data=data, attributes=attrs) # Delegate the publishing to the batch. batch = self._batch(topic) future = None while future is None: future = batch.publish(message) if future is None: batch = self._batch(topic, create=True) return future