def publish(self, message): """Publish a single message. 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. This method is called by :meth:`~.PublisherClient.publish`. Args: message (~.pubsub_v1.types.PubsubMessage): The Pub/Sub message. Returns: ~.pubsub_v1.publisher.futures.Future: An object conforming to the :class:`concurrent.futures.Future` interface. """ # Coerce the type, just in case. if not isinstance(message, types.PubsubMessage): message = types.PubsubMessage(**message) # Add the size to the running total of the size, so we know # if future messages need to be rejected. self._size += message.ByteSize() # Store the actual message in the batch's message queue. self._messages.append(message) # Return a Future. That future needs to be aware of the status # of this batch. f = futures.Future() self._futures.append(f) return f
def publish(self, message): """Publish a single message. 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. If the batch is full or the commit is already in progress, the method does not do anything. This method is called by :meth:`~.PublisherClient.publish`. Args: message (~.pubsub_v1.types.PubsubMessage): The Pub/Sub message. Returns: Optional[~google.api_core.future.Future]: An object conforming to the :class:`~concurrent.futures.Future` interface or :data:`None`. If :data:`None` is returned, that signals that the batch cannot accept a message. """ # Coerce the type, just in case. if not isinstance(message, types.PubsubMessage): message = types.PubsubMessage(**message) future = None with self._state_lock: if not self.will_accept(message): return future new_size = self._size + message.ByteSize() new_count = len(self._messages) + 1 overflow = ( new_size > self.settings.max_bytes or new_count >= self._settings.max_messages ) if not self._messages or not overflow: # Store the actual message in the batch's message queue. self._messages.append(message) self._size = new_size # Track the future on this batch (so that the result of the # future can be set). future = futures.Future(completed=threading.Event()) self._futures.append(future) # Try to commit, but it must be **without** the lock held, since # ``commit()`` will try to obtain the lock. if overflow: self.commit() return future
def publish(self, message): """Publish message. This publishes messages immediately--it does no batching and requires no threads. """ if not isinstance(message, PubsubMessage): message = PubsubMessage(**message) # Publish and return result wrapped in future future = futures.Future() resp = self._client.api.publish(self._topic, [message], timeout=5) future.set_result(resp.message_ids[0]) return future
def publish(self, message): """Publish a single message. 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. This method is called by :meth:`~.PublisherClient.publish`. Args: message (~.pubsub_v1.types.PubsubMessage): The Pub/Sub message. Returns: Optional[~google.api_core.future.Future]: An object conforming to the :class:`~concurrent.futures.Future` interface or :data:`None`. If :data:`None` is returned, that signals that the batch cannot accept a message. """ # Coerce the type, just in case. if not isinstance(message, types.PubsubMessage): message = types.PubsubMessage(**message) with self._state_lock: if not self.will_accept(message): return None # Add the size to the running total of the size, so we know # if future messages need to be rejected. self._size += message.ByteSize() # Store the actual message in the batch's message queue. self._messages.append(message) # Track the future on this batch (so that the result of the # future can be set). future = futures.Future(completed=threading.Event()) self._futures.append(future) # Determine the number of messages before releasing the lock. num_messages = len(self._messages) # Try to commit, but it must be **without** the lock held, since # ``commit()`` will try to obtain the lock. if num_messages >= self._settings.max_messages: self.commit() return future
def publish(self, message): """Publish a single message. 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. If the batch is full or the commit is already in progress, the method does not do anything. This method is called by :meth:`~.PublisherClient.publish`. Args: message (~.pubsub_v1.types.PubsubMessage): The Pub/Sub message. Returns: Optional[~google.api_core.future.Future]: An object conforming to the :class:`~concurrent.futures.Future` interface or :data:`None`. If :data:`None` is returned, that signals that the batch cannot accept a message. Raises: pubsub_v1.publisher.exceptions.MessageTooLargeError: If publishing the ``message`` would exceed the max size limit on the backend. """ # Coerce the type, just in case. if not isinstance(message, gapic_types.PubsubMessage): # For performance reasons, the message should be constructed by directly # using the raw protobuf class, and only then wrapping it into the # higher-level PubsubMessage class. vanilla_pb = _raw_proto_pubbsub_message(**message) message = gapic_types.PubsubMessage.wrap(vanilla_pb) future = None with self._state_lock: assert (self._status != base.BatchStatus.ERROR ), "Publish after stop() or publish error." if self.status != base.BatchStatus.ACCEPTING_MESSAGES: return size_increase = gapic_types.PublishRequest( messages=[message])._pb.ByteSize() if (self._base_request_size + size_increase) > _SERVER_PUBLISH_MAX_BYTES: err_msg = ( "The message being published would produce too large a publish " "request that would exceed the maximum allowed size on the " "backend ({} bytes).".format(_SERVER_PUBLISH_MAX_BYTES)) raise exceptions.MessageTooLargeError(err_msg) new_size = self._size + size_increase new_count = len(self._messages) + 1 size_limit = min(self.settings.max_bytes, _SERVER_PUBLISH_MAX_BYTES) overflow = new_size > size_limit or new_count >= self.settings.max_messages if not self._messages or not overflow: # Store the actual message in the batch's message queue. self._messages.append(message) self._size = new_size # Track the future on this batch (so that the result of the # future can be set). future = futures.Future() self._futures.append(future) # Try to commit, but it must be **without** the lock held, since # ``commit()`` will try to obtain the lock. if self._commit_when_full and overflow: self.commit() return future
def publish(self, topic, data, ordering_key="", retry=gapic_v1.method.DEFAULT, **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. This method may block if LimitExceededBehavior.BLOCK is used in the flow control settings. 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='******') Args: topic (str): The topic to publish messages to. data (bytes): A bytestring representing the message body. This must be a bytestring. ordering_key: A string that identifies related messages for which publish order should be respected. Message ordering must be enabled for this client to use this feature. retry (Optional[google.api_core.retry.Retry]): Designation of what errors, if any, should be retried. If `ordering_key` is specified, the total retry deadline will be changed to "infinity". 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). Raises: RuntimeError: If called after publisher has been stopped by a `stop()` method call. pubsub_v1.publisher.exceptions.MessageTooLargeError: If publishing the ``message`` would exceed the max size limit on the backend. """ # Sanity check: Is the data being sent as a bytestring? # If it is literally anything else, complain loudly about it. if not isinstance(data, bytes): raise TypeError( "Data being published to Pub/Sub must be sent as a bytestring." ) if not self._enable_message_ordering and ordering_key != "": raise ValueError( "Cannot publish a message with an ordering key when message " "ordering is not enabled.") # Coerce all attributes to text strings. for k, v in copy.copy(attrs).items(): if isinstance(v, str): continue if isinstance(v, bytes): 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. For performance reasons, the message # should be constructed by directly using the raw protobuf class, and only # then wrapping it into the higher-level PubsubMessage class. vanilla_pb = _raw_proto_pubbsub_message(data=data, ordering_key=ordering_key, attributes=attrs) message = gapic_types.PubsubMessage.wrap(vanilla_pb) # Messages should go through flow control to prevent excessive # queuing on the client side (depending on the settings). try: self._flow_controller.add(message) except exceptions.FlowControlLimitError as exc: future = futures.Future() future.set_exception(exc) return future def on_publish_done(future): self._flow_controller.release(message) with self._batch_lock: if self._is_stopped: raise RuntimeError("Cannot publish on a stopped publisher.") # Set retry timeout to "infinite" when message ordering is enabled. # Note that this then also impacts messages added with an empty # ordering key. if self._enable_message_ordering: if retry is gapic_v1.method.DEFAULT: # use the default retry for the publish GRPC method as a base transport = self.api._transport retry = transport._wrapped_methods[ transport.publish]._retry retry = retry.with_deadline(2.0**32) # Delegate the publishing to the sequencer. sequencer = self._get_or_create_sequencer(topic, ordering_key) future = sequencer.publish(message, retry=retry) future.add_done_callback(on_publish_done) # Create a timer thread if necessary to enforce the batching # timeout. self._ensure_commit_timer_runs_no_lock() return future
def publish(self, topic, data, ordering_key="", **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='******') Args: topic (str): The topic to publish messages to. data (bytes): A bytestring representing the message body. This must be a bytestring. ordering_key: A string that identifies related messages for which publish order should be respected. Message ordering must be enabled for this client to use this feature. EXPERIMENTAL: This feature is currently available in a closed alpha. Please contact the Cloud Pub/Sub team to use it. 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). Raises: RuntimeError: If called after publisher has been stopped by a `stop()` method call. pubsub_v1.publisher.exceptions.MessageTooLargeError: If publishing the ``message`` would exceed the max size limit on the backend. """ # 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." ) if not self._enable_message_ordering and ordering_key != "": raise ValueError( "Cannot publish a message with an ordering key when message " "ordering is not enabled.") # 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, ordering_key=ordering_key, attributes=attrs) # Messages should go through flow control to prevent excessive # queuing on the client side (depending on the settings). try: self._flow_controller.add(message) except exceptions.FlowControlLimitError as exc: future = futures.Future() future.set_exception(exc) return future def on_publish_done(future): self._flow_controller.release(message) with self._batch_lock: if self._is_stopped: raise RuntimeError("Cannot publish on a stopped publisher.") sequencer = self._get_or_create_sequencer(topic, ordering_key) # Delegate the publishing to the sequencer. future = sequencer.publish(message) future.add_done_callback(on_publish_done) # Create a timer thread if necessary to enforce the batching # timeout. self._ensure_commit_timer_runs_no_lock() return future
def publish(self, message): """Publish a single message. 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. If the batch is full or the commit is already in progress, the method does not do anything. This method is called by :meth:`~.PublisherClient.publish`. Args: message (~.pubsub_v1.types.PubsubMessage): The Pub/Sub message. Returns: Optional[~google.api_core.future.Future]: An object conforming to the :class:`~concurrent.futures.Future` interface or :data:`None`. If :data:`None` is returned, that signals that the batch cannot accept a message. Raises: pubsub_v1.publisher.exceptions.MessageTooLargeError: If publishing the ``message`` would exceed the max size limit on the backend. """ # Coerce the type, just in case. if not isinstance(message, types.PubsubMessage): message = types.PubsubMessage(**message) future = None with self._state_lock: if not self.will_accept(message): return future size_increase = types.PublishRequest(messages=[message]).ByteSize() if (self._base_request_size + size_increase) > _SERVER_PUBLISH_MAX_BYTES: err_msg = ( "The message being published would produce too large a publish " "request that would exceed the maximum allowed size on the " "backend ({} bytes).".format(_SERVER_PUBLISH_MAX_BYTES)) raise exceptions.MessageTooLargeError(err_msg) new_size = self._size + size_increase new_count = len(self._messages) + 1 size_limit = min(self.settings.max_bytes, _SERVER_PUBLISH_MAX_BYTES) overflow = new_size > size_limit or new_count >= self.settings.max_messages if not self._messages or not overflow: # Store the actual message in the batch's message queue. self._messages.append(message) self._size = new_size # Track the future on this batch (so that the result of the # future can be set). future = futures.Future(completed=threading.Event()) self._futures.append(future) # Try to commit, but it must be **without** the lock held, since # ``commit()`` will try to obtain the lock. if overflow: self.commit() return future
def test_result_on_failure(self): future = futures.Future() future.set_exception(RuntimeError("Something bad happened.")) with pytest.raises(RuntimeError): future.result()
def test_result_on_success(self): future = futures.Future() future.set_result("570307942214048") assert future.result() == "570307942214048"