<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def publish(self, exchange, routing_key, body, properties=None):
 """Publish a message to RabbitMQ. If the RabbitMQ connection is not established or is blocked, attempt to wait until sending is possible. :param str exchange: The exchange to publish the message to. :param str routing_key: The routing key to publish the message with. :param bytes body: The message body to send. :param dict properties: An optional dict of additional properties to append. :rtype: tornado.concurrent.Future :raises: :exc:`sprockets.mixins.amqp.NotReadyError` :raises: :exc:`sprockets.mixins.amqp.PublishingError` """

        future = concurrent.Future()

        properties = properties or {}
        properties.setdefault('app_id', self.default_app_id)
        properties.setdefault('message_id', str(uuid.uuid4()))
        properties.setdefault('timestamp', int(time.time()))

        if self.ready:
            if self.publisher_confirmations:
                self.message_number += 1
                self.messages[self.message_number] = future
            else:
                future.set_result(None)

            try:
                self.channel.basic_publish(
                    exchange, routing_key, body,
                    pika.BasicProperties(**properties), True)
            except exceptions.AMQPError as error:
                future.set_exception(
                    PublishingFailure(
                        properties['message_id'],
                        exchange, routing_key,
                        error.__class__.__name__))
        else:
            future.set_exception(NotReadyError(
                self.state_description, properties['message_id']))
        return future