async def receive_deferred_messages(self, sequence_numbers): # type: (Union[int, List[int]]) -> List[ReceivedMessage] """Receive messages that have previously been deferred. When receiving deferred messages from a partitioned entity, all of the supplied sequence numbers must be messages from the same partition. :param Union[int, list[int]] sequence_numbers: A list of the sequence numbers of messages that have been deferred. :rtype: list[~azure.servicebus.aio.ReceivedMessage] .. admonition:: Example: .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py :start-after: [START receive_defer_async] :end-before: [END receive_defer_async] :language: python :dedent: 4 :caption: Receive deferred messages from ServiceBus. """ self._check_live() if isinstance(sequence_numbers, six.integer_types): sequence_numbers = [sequence_numbers] if not sequence_numbers: raise ValueError("At least one sequence number must be specified.") await self._open() try: receive_mode = self._receive_mode.value.value except AttributeError: receive_mode = int(self._receive_mode) message = { MGMT_REQUEST_SEQUENCE_NUMBERS: types.AMQPArray([types.AMQPLong(s) for s in sequence_numbers]), MGMT_REQUEST_RECEIVER_SETTLE_MODE: types.AMQPuInt(receive_mode) } self._populate_message_properties(message) handler = functools.partial(mgmt_handlers.deferred_message_op, receive_mode=self._receive_mode, message_type=ReceivedMessage, receiver=self) messages = await self._mgmt_request_response_with_retry( REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, message, handler ) return messages
async def receive_deferred_messages(self, sequence_numbers, mode=ReceiveSettleMode.PeekLock): """Receive messages that have previously been deferred. This operation can only receive deferred messages from the current session. When receiving deferred messages from a partitioned entity, all of the supplied sequence numbers must be messages from the same partition. :param sequence_numbers: A list of the sequence numbers of messages that have been deferred. :type sequence_numbers: list[int] :param mode: The receive mode, default value is PeekLock. :type mode: ~azure.servicebus.common.constants.ReceiveSettleMode :rtype: list[~azure.servicebus.aio.async_message.DeferredMessage] .. admonition:: Example: .. literalinclude:: ../samples/async_samples/test_examples_async.py :start-after: [START receiver_defer_session_messages] :end-before: [END receiver_defer_session_messages] :language: python :dedent: 8 :caption: Defer messages, then retrieve them by sequence number. """ if not sequence_numbers: raise ValueError("At least one sequence number must be specified.") await self._can_run() try: receive_mode = mode.value.value except AttributeError: receive_mode = int(mode) message = { 'sequence-numbers': types.AMQPArray([types.AMQPLong(s) for s in sequence_numbers]), 'receiver-settle-mode': types.AMQPuInt(receive_mode), 'session-id': self.session_id } handler = functools.partial(mgmt_handlers.deferred_message_op, mode=receive_mode, message_type=DeferredMessage) messages = await self._mgmt_request_response( REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, message, handler) for m in messages: m._receiver = self # pylint: disable=protected-access return messages
def _settle_message(self, settlement, lock_tokens, dead_letter_details=None): # type: (bytes, List[str], Optional[Dict[str, Any]]) -> Any message = { MGMT_REQUEST_DISPOSITION_STATUS: settlement, MGMT_REQUEST_LOCK_TOKENS: types.AMQPArray(lock_tokens) } self._populate_message_properties(message) if dead_letter_details: message.update(dead_letter_details) return self._mgmt_request_response_with_retry( REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, message, mgmt_handlers.default )
def _settle_message(self, settlement, lock_tokens, dead_letter_details=None): message = { MGMT_REQUEST_DISPOSITION_STATUS: settlement, MGMT_REQUEST_LOCK_TOKENS: types.AMQPArray(lock_tokens) } if self._session_id: message[MGMT_REQUEST_SESSION_ID] = self._session_id if dead_letter_details: message.update(dead_letter_details) return self._mgmt_request_response_with_retry( REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, message, mgmt_handlers.default )
async def _settle_message_via_mgmt_link(self, settlement, lock_tokens, dead_letter_details=None): message = { MGMT_REQUEST_DISPOSITION_STATUS: settlement, MGMT_REQUEST_LOCK_TOKENS: types.AMQPArray(lock_tokens) } self._populate_message_properties(message) if dead_letter_details: message.update(dead_letter_details) return await self._mgmt_request_response_with_retry( REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, message, mgmt_handlers.default)
async def receive_deferred_messages(self, sequence_numbers): # type: (List[int]) -> List[ReceivedMessage] """Receive messages that have previously been deferred. When receiving deferred messages from a partitioned entity, all of the supplied sequence numbers must be messages from the same partition. :param list[int] sequence_numbers: A list of the sequence numbers of messages that have been deferred. :rtype: list[~azure.servicebus.aio.ReceivedMessage] .. admonition:: Example: .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py :start-after: [START receive_defer_async] :end-before: [END receive_defer_async] :language: python :dedent: 4 :caption: Receive deferred messages from ServiceBus. """ self._can_run() if not sequence_numbers: raise ValueError("At least one sequence number must be specified.") await self._open() try: receive_mode = self._mode.value.value except AttributeError: receive_mode = int(self._mode) message = { MGMT_REQUEST_SEQUENCE_NUMBERS: types.AMQPArray([types.AMQPLong(s) for s in sequence_numbers]), MGMT_REQUEST_RECEIVER_SETTLE_MODE: types.AMQPuInt(receive_mode) } if self._session_id: message[MGMT_REQUEST_SESSION_ID] = self._session_id handler = functools.partial(mgmt_handlers.deferred_message_op, mode=self._mode, message_type=ReceivedMessage) messages = await self._mgmt_request_response_with_retry( REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, message, handler) for m in messages: m._receiver = self # pylint: disable=protected-access return messages
async def cancel_scheduled_messages(self, sequence_numbers: Union[int, List[int]], *, timeout: Optional[float] = None, **kwargs: Any) -> None: """ Cancel one or more messages that have previously been scheduled and are still pending. :param sequence_numbers: The sequence numbers of the scheduled messages. :type sequence_numbers: int or list[int] :keyword float timeout: The total operation timeout in seconds including all the retries. The value must be greater than 0 if specified. The default value is None, meaning no timeout. :rtype: None :raises: ~azure.servicebus.exceptions.ServiceBusError if messages cancellation failed due to message already cancelled or enqueued. .. admonition:: Example: .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py :start-after: [START cancel_scheduled_messages_async] :end-before: [END cancel_scheduled_messages_async] :language: python :dedent: 4 :caption: Cancelling messages scheduled to be sent in future """ if kwargs: warnings.warn(f"Unsupported keyword args: {kwargs}") self._check_live() if timeout is not None and timeout <= 0: raise ValueError("The timeout must be greater than 0.") if isinstance(sequence_numbers, int): numbers = [types.AMQPLong(sequence_numbers)] else: numbers = [types.AMQPLong(s) for s in sequence_numbers] if len(numbers) == 0: return None # no-op on empty list. request_body = { MGMT_REQUEST_SEQUENCE_NUMBERS: types.AMQPArray(numbers) } return await self._mgmt_request_response_with_retry( REQUEST_RESPONSE_CANCEL_SCHEDULED_MESSAGE_OPERATION, request_body, mgmt_handlers.default, timeout=timeout, )
def _settle_message_via_mgmt_link(self, settlement, lock_tokens, dead_letter_details=None): # type: (str, List[Union[uuid.UUID, str]], Optional[Dict[str, Any]]) -> Any message = { MGMT_REQUEST_DISPOSITION_STATUS: settlement, MGMT_REQUEST_LOCK_TOKENS: types.AMQPArray(lock_tokens) } self._populate_message_properties(message) if dead_letter_details: message.update(dead_letter_details) # We don't do retry here, retry is done in the ServiceBusReceivedMessage._settle_message return self._mgmt_request_response( REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, message, mgmt_handlers.default )
async def test_event_hubs_idempotent_producer(live_eventhub_config): uri = "sb://{}/{}".format(live_eventhub_config['hostname'], live_eventhub_config['event_hub']) sas_auth = authentication.SASTokenAsync.from_shared_access_key( uri, live_eventhub_config['key_name'], live_eventhub_config['access_key']) target = "amqps://{}/{}/Partitions/0".format( live_eventhub_config['hostname'], live_eventhub_config['event_hub']) symbol_array = [ uamqp_types.AMQPSymbol(b"com.microsoft:idempotent-producer") ] desired_capabilities = utils.data_factory( uamqp_types.AMQPArray(symbol_array)) link_properties = { uamqp_types.AMQPSymbol(b"com.microsoft:timeout"): uamqp_types.AMQPLong(int(60 * 1000)) } def on_attach(attach_source, attach_target, properties, error): if str(attach_target) == target: on_attach.owner_level = properties.get( b"com.microsoft:producer-epoch") on_attach.producer_group_id = properties.get( b"com.microsoft:producer-id") on_attach.starting_sequence_number = properties.get( b"com.microsoft:producer-sequence-number") send_client = uamqp.SendClientAsync( target, auth=sas_auth, desired_capabilities=desired_capabilities, link_properties=link_properties, on_attach=on_attach, debug=True) await send_client.open_async() while not await send_client.client_ready_async(): await asyncio.sleep(0.05) assert on_attach.owner_level is not None assert on_attach.producer_group_id is not None assert on_attach.starting_sequence_number is not None await send_client.close_async()
def receive_deferred_messages(self, sequence_numbers, mode=ReceiveSettleMode.PeekLock, **kwargs): """Receive messages by sequence number that have been previously deferred. When receiving deferred messages from a partitioned entity, all of the supplied sequence numbers must be messages from the same partition. :param sequence_numbers: A list of the sequence numbers of messages that have been deferred. :type sequence_numbers: list[int] :param mode: The mode with which messages will be retrieved from the entity. The two options are PeekLock and ReceiveAndDelete. Messages received with PeekLock must be settled within a given lock period before they will be removed from the queue. Messages received with ReceiveAndDelete will be immediately removed from the queue, and cannot be subsequently rejected or re-received if the client fails to process the message. The default mode is PeekLock. :type mode: ~azure.servicebus.common.constants.ReceiveSettleMode :rtype: list[~azure.servicebus.common.message.Message] .. admonition:: Example: .. literalinclude:: ../samples/sync_samples/test_examples.py :start-after: [START receive_deferred_messages_service_bus] :end-before: [END receive_deferred_messages_service_bus] :language: python :dedent: 8 :caption: Get the messages which were deferred using their sequence numbers """ if (self.entity and self.requires_session) or kwargs.get('session'): raise ValueError("Sessionful deferred messages can only be received within a locked receive session.") if not sequence_numbers: raise ValueError("At least one sequence number must be specified.") try: receive_mode = mode.value.value except AttributeError: receive_mode = int(mode) message = { 'sequence-numbers': types.AMQPArray([types.AMQPLong(s) for s in sequence_numbers]), 'receiver-settle-mode': types.AMQPuInt(receive_mode)} mgmt_handler = functools.partial(mgmt_handlers.deferred_message_op, mode=receive_mode) with BaseHandler(self.entity_uri, self.auth_config, debug=self.debug, **kwargs) as handler: return handler._mgmt_request_response( # pylint: disable=protected-access REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, message, mgmt_handler)
async def settle_deferred_messages(self, settlement, messages, **kwargs): """Settle messages that have been previously deferred. :param settlement: How the messages are to be settled. This must be a string of one of the following values: 'completed', 'suspended', 'abandoned'. :type settlement: str :param messages: A list of deferred messages to be settled. :type messages: list[~azure.servicebus.aio.async_message.DeferredMessage] Example: .. literalinclude:: ../examples/async_examples/test_examples_async.py :start-after: [START client_settle_deferred_messages] :end-before: [END client_settle_deferred_messages] :language: python :dedent: 4 :caption: Settle deferred messages. """ if (self.entity and self.requires_session) or kwargs.get('session'): raise ValueError( "Sessionful deferred messages can only be settled within a locked receive session." ) if settlement.lower() not in ['completed', 'suspended', 'abandoned']: raise ValueError( "Settlement must be one of: 'completed', 'suspended', 'abandoned'" ) if not messages: raise ValueError("At least one message must be specified.") message = { 'disposition-status': settlement.lower(), 'lock-tokens': types.AMQPArray([m.lock_token for m in messages]) } async with BaseHandler(self.entity_uri, self.auth_config, loop=self.loop, debug=self.debug, **kwargs) as handler: return await handler._mgmt_request_response( # pylint: disable=protected-access REQUEST_RESPONSE_UPDATE_DISPOSTION_OPERATION, message, mgmt_handlers.default)
def test_event_hubs_client_receive_with_runtime_metric_sync( live_eventhub_config): uri = "sb://{}/{}".format(live_eventhub_config['hostname'], live_eventhub_config['event_hub']) sas_auth = authentication.SASTokenAuth.from_shared_access_key( uri, live_eventhub_config['key_name'], live_eventhub_config['access_key']) source = "amqps://{}/{}/ConsumerGroups/{}/Partitions/{}".format( live_eventhub_config['hostname'], live_eventhub_config['event_hub'], live_eventhub_config['consumer_group'], live_eventhub_config['partition']) receiver_runtime_metric_symbol = b'com.microsoft:enable-receiver-runtime-metric' symbol_array = [types.AMQPSymbol(receiver_runtime_metric_symbol)] desired_capabilities = utils.data_factory(types.AMQPArray(symbol_array)) with uamqp.ReceiveClient( source, auth=sas_auth, debug=False, timeout=50, prefetch=50, desired_capabilities=desired_capabilities) as receive_client: log.info("Created client, receiving...") with pytest.raises(ValueError): batch = receive_client.receive_message_batch(max_batch_size=100) batch = receive_client.receive_message_batch(max_batch_size=10) log.info("Got batch: {}".format(len(batch))) assert len(batch) <= 10 for message in batch: annotations = message.annotations delivery_annotations = message.delivery_annotations log.info("Sequence Number: {}".format( annotations.get(b'x-opt-sequence-number'))) assert b'last_enqueued_sequence_number' in delivery_annotations assert b'last_enqueued_offset' in delivery_annotations assert b'last_enqueued_time_utc' in delivery_annotations assert b'runtime_info_retrieval_time_utc' in delivery_annotations log.info("Finished receiving")
async def cancel_scheduled_messages(self, *sequence_numbers): """Cancel one or more messages that have previsouly been scheduled and are still pending. :param sequence_numbers: The seqeuence numbers of the scheduled messages. :type sequence_numbers: int Example: .. literalinclude:: ../examples/async_examples/test_examples_async.py :start-after: [START cancel_schedule_messages] :end-before: [END cancel_schedule_messages] :language: python :dedent: 4 :caption: Schedule messages. """ if not self.running: await self.open() numbers = [types.AMQPLong(s) for s in sequence_numbers] request_body = {'sequence-numbers': types.AMQPArray(numbers)} return await self._mgmt_request_response( REQUEST_RESPONSE_CANCEL_SCHEDULED_MESSAGE_OPERATION, request_body, mgmt_handlers.default)
def _create_handler(self): source = Source(self._source) if self._offset is not None: source.set_filter(self._offset._selector()) # pylint:disable=protected-access if StrictVersion(uamqp.__version__) < StrictVersion( "1.2.3"): # backward compatible until uamqp 1.2.3 released desired_capabilities = {} elif self._track_last_enqueued_event_properties: symbol_array = [ types.AMQPSymbol(self._receiver_runtime_metric_symbol) ] desired_capabilities = { "desired_capabilities": utils.data_factory(types.AMQPArray(symbol_array)) } else: desired_capabilities = {"desired_capabilities": None} self._handler = ReceiveClientAsync( source, auth=self._client._create_auth(), # pylint:disable=protected-access debug=self._client._config.network_tracing, # pylint:disable=protected-access prefetch=self._prefetch, link_properties=self._link_properties, timeout=self._timeout, error_policy=self._retry_policy, keep_alive_interval=self._keep_alive, client_name=self._name, receive_settle_mode=uamqp.constants.ReceiverSettleMode. ReceiveAndDelete, auto_complete=False, properties=self._client._create_properties( # pylint:disable=protected-access self._client._config.user_agent), # pylint:disable=protected-access **desired_capabilities, # pylint:disable=protected-access loop=self._loop) self._messages_iter = None
def _create_handler(self, auth): # type: (JWTTokenAuth) -> None source = Source(self._source) if self._offset is not None: source.set_filter( event_position_selector(self._offset, self._offset_inclusive) ) desired_capabilities = None if self._track_last_enqueued_event_properties: symbol_array = [types.AMQPSymbol(RECEIVER_RUNTIME_METRIC_SYMBOL)] desired_capabilities = utils.data_factory(types.AMQPArray(symbol_array)) properties = create_properties( self._client._config.user_agent # pylint:disable=protected-access ) self._handler = ReceiveClient( source, auth=auth, debug=self._client._config.network_tracing, # pylint:disable=protected-access prefetch=self._prefetch, link_properties=self._link_properties, timeout=self._timeout, idle_timeout=self._idle_timeout, error_policy=self._retry_policy, keep_alive_interval=self._keep_alive, client_name=self._name, receive_settle_mode=uamqp.constants.ReceiverSettleMode.ReceiveAndDelete, auto_complete=False, properties=properties, desired_capabilities=desired_capabilities, ) self._handler._streaming_receive = True # pylint:disable=protected-access self._handler._message_received_callback = ( # pylint:disable=protected-access self._message_received )
async def receive_deferred_messages( self, sequence_numbers: Union[int, List[int]], **kwargs: Any ) -> List[ServiceBusReceivedMessage]: """Receive messages that have previously been deferred. When receiving deferred messages from a partitioned entity, all of the supplied sequence numbers must be messages from the same partition. :param Union[int, list[int]] sequence_numbers: A list of the sequence numbers of messages that have been deferred. :keyword Optional[float] timeout: The total operation timeout in seconds including all the retries. The value must be greater than 0 if specified. The default value is None, meaning no timeout. :rtype: list[~azure.servicebus.aio.ServiceBusReceivedMessage] .. admonition:: Example: .. literalinclude:: ../samples/async_samples/sample_code_servicebus_async.py :start-after: [START receive_defer_async] :end-before: [END receive_defer_async] :language: python :dedent: 4 :caption: Receive deferred messages from ServiceBus. """ self._check_live() timeout = kwargs.pop("timeout", None) if timeout is not None and timeout <= 0: raise ValueError("The timeout must be greater than 0.") if isinstance(sequence_numbers, six.integer_types): sequence_numbers = [sequence_numbers] if len(sequence_numbers) == 0: return [] # no-op on empty list. await self._open() uamqp_receive_mode = ServiceBusToAMQPReceiveModeMap[self._receive_mode] try: receive_mode = uamqp_receive_mode.value.value except AttributeError: receive_mode = int(uamqp_receive_mode.value) message = { MGMT_REQUEST_SEQUENCE_NUMBERS: types.AMQPArray( [types.AMQPLong(s) for s in sequence_numbers] ), MGMT_REQUEST_RECEIVER_SETTLE_MODE: types.AMQPuInt(receive_mode), } self._populate_message_properties(message) handler = functools.partial( mgmt_handlers.deferred_message_op, receive_mode=self._receive_mode, message_type=ServiceBusReceivedMessage, receiver=self, ) messages = await self._mgmt_request_response_with_retry( REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, message, handler, timeout=timeout, ) links = get_receive_links(message) with receive_trace_context_manager( self, span_name=SPAN_NAME_RECEIVE_DEFERRED, links=links ): if ( self._auto_lock_renewer and not self._session and self._receive_mode != ServiceBusReceiveMode.RECEIVE_AND_DELETE ): for message in messages: self._auto_lock_renewer.register(self, message) return messages
def _renew_locks(self, *lock_tokens): # type: (*str) -> Any message = {MGMT_REQUEST_LOCK_TOKENS: types.AMQPArray(lock_tokens)} return self._mgmt_request_response_with_retry( REQUEST_RESPONSE_RENEWLOCK_OPERATION, message, mgmt_handlers.lock_renew_op)
def _renew_locks(self, *lock_tokens): message = {'lock-tokens': types.AMQPArray(lock_tokens)} return self._mgmt_request_response( REQUEST_RESPONSE_RENEWLOCK_OPERATION, message, mgmt_handlers.lock_renew_op)