diff --git a/sdk/servicebus/azure-servicebus/CHANGELOG.md b/sdk/servicebus/azure-servicebus/CHANGELOG.md index 78d20d3ba2e9..b5642d0ac95a 100644 --- a/sdk/servicebus/azure-servicebus/CHANGELOG.md +++ b/sdk/servicebus/azure-servicebus/CHANGELOG.md @@ -4,14 +4,12 @@ **Breaking Changes** -* `ServiceBusSender` and `ServiceBusReceiver` are no longer reusable and will raise `ValueError` when trying to operate on a closed handler. +* `ServiceBusSender` and `ServiceBusReceiver` are no more reusable and will raise `ValueError` when trying to operate on a closed handler. +* `send_messages`, `schedule_messages`, `cancel_scheduled_messages` and `receive_deferred_messages` now performs a no-op rather than raising a `ValueError` if provided an empty list of messages or an empty batch. **BugFixes** * FQDNs and Connection strings are now supported even with strippable whitespace or protocol headers (e.g. 'sb://'). - -**Bug Fixes** - * Using parameter `auto_lock_renewer` on a sessionful receiver alongside `ReceiveMode.ReceiveAndDelete` will no longer fail during receipt due to failure to register the message with the renewer. ## 7.0.0b8 (2020-11-05) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py index 6c8e8d858fbe..ac0bafe20ccc 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py @@ -555,8 +555,8 @@ def receive_deferred_messages(self, sequence_numbers, **kwargs): raise ValueError("The timeout must be greater than 0.") 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.") + if len(sequence_numbers) == 0: + return [] # no-op on empty list. self._open() try: receive_mode = self._receive_mode.value.value diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py index f605fe22a83f..e5c8f4ce9f6c 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py @@ -259,6 +259,8 @@ def schedule_messages(self, messages, schedule_time_utc, **kwargs): if isinstance(messages, ServiceBusMessage): request_body = self._build_schedule_request(schedule_time_utc, messages) else: + if len(messages) == 0: + return [] # No-op on empty list. request_body = self._build_schedule_request(schedule_time_utc, *messages) return self._mgmt_request_response_with_retry( REQUEST_RESPONSE_SCHEDULE_MESSAGE_OPERATION, @@ -296,6 +298,8 @@ def cancel_scheduled_messages(self, sequence_numbers, **kwargs): 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 self._mgmt_request_response_with_retry( REQUEST_RESPONSE_CANCEL_SCHEDULED_MESSAGE_OPERATION, @@ -347,7 +351,7 @@ def send_messages(self, message, **kwargs): except TypeError: # Message was not a list or generator. pass if isinstance(message, ServiceBusMessageBatch) and len(message) == 0: # pylint: disable=len-as-condition - raise ValueError("A ServiceBusMessageBatch or list of Message must have at least one Message") + return # Short circuit noop if an empty list or batch is provided. if not isinstance(message, ServiceBusMessageBatch) and not isinstance(message, ServiceBusMessage): raise TypeError( "Can only send azure.servicebus. or " diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py index bf91d3a951c0..7eaafc639cdb 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py @@ -557,8 +557,8 @@ async def receive_deferred_messages( raise ValueError("The timeout must be greater than 0.") 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.") + if len(sequence_numbers) == 0: + return [] # no-op on empty list. await self._open() try: receive_mode = self._receive_mode.value.value diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py index 36178a78663b..478120c3d39b 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py @@ -204,6 +204,8 @@ async def schedule_messages( if isinstance(messages, ServiceBusMessage): request_body = self._build_schedule_request(schedule_time_utc, messages) else: + if len(messages) == 0: + return [] # No-op on empty list. request_body = self._build_schedule_request(schedule_time_utc, *messages) return await self._mgmt_request_response_with_retry( REQUEST_RESPONSE_SCHEDULE_MESSAGE_OPERATION, @@ -240,6 +242,8 @@ async def cancel_scheduled_messages(self, sequence_numbers: Union[int, List[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, @@ -294,7 +298,7 @@ async def send_messages( except TypeError: # Message was not a list or generator. pass if isinstance(message, ServiceBusMessageBatch) and len(message) == 0: # pylint: disable=len-as-condition - raise ValueError("A ServiceBusMessageBatch or list of Message must have at least one Message") + return # Short circuit noop if an empty list or batch is provided. if not isinstance(message, ServiceBusMessageBatch) and not isinstance(message, ServiceBusMessage): raise TypeError( "Can only send azure.servicebus." diff --git a/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py b/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py index 478f65a90e92..1c3081d34a3e 100644 --- a/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py +++ b/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py @@ -67,6 +67,13 @@ async def test_async_queue_by_queue_client_conn_str_receive_handler_peeklock(sel message = ServiceBusMessage("Handler message no. {}".format(i)) await sender.send_messages(message, timeout=5) + # Test that noop empty send works properly. + await sender.send_messages([]) + await sender.send_messages(ServiceBusMessageBatch()) + assert len(await sender.schedule_messages([], utc_now())) == 0 + await sender.cancel_scheduled_messages([]) + + # Then test expected failure modes. with pytest.raises(ValueError): async with sender: raise AssertionError("Should raise ValueError") @@ -85,6 +92,7 @@ async def test_async_queue_by_queue_client_conn_str_receive_handler_peeklock(sel receiver = sb_client.get_queue_receiver(servicebus_queue.name, max_wait_time=5) async with receiver: + assert len(await receiver.receive_deferred_messages([])) == 0 with pytest.raises(ValueError): await receiver.receive_messages(max_wait_time=0) diff --git a/sdk/servicebus/azure-servicebus/tests/test_queues.py b/sdk/servicebus/azure-servicebus/tests/test_queues.py index 5ec9a3698ac1..b6f303b9a0ff 100644 --- a/sdk/servicebus/azure-servicebus/tests/test_queues.py +++ b/sdk/servicebus/azure-servicebus/tests/test_queues.py @@ -128,8 +128,15 @@ def test_queue_by_queue_client_conn_str_receive_handler_peeklock(self, servicebu message.to = 'to' message.reply_to = 'reply_to' sender.send_messages(message) + + # Test that noop empty send works properly. + sender.send_messages([]) + sender.send_messages(ServiceBusMessageBatch()) + assert len(sender.schedule_messages([], utc_now())) == 0 + sender.cancel_scheduled_messages([]) sender.close() + # Then test expected failure modes. with pytest.raises(ValueError): with sender: raise AssertionError("Should raise ValueError") @@ -145,6 +152,7 @@ def test_queue_by_queue_client_conn_str_receive_handler_peeklock(self, servicebu receiver = sb_client.get_queue_receiver(servicebus_queue.name, max_wait_time=5) + assert len(receiver.receive_deferred_messages([])) == 0 with pytest.raises(ValueError): receiver.receive_messages(max_wait_time=0)