rabbitpy is a pure python, thread-safe [1], and pythonic BSD Licensed AMQP/RabbitMQ
library that supports Python 2.7+ and 3.4+. rabbitpy aims to provide a simple
and easy to use API for interfacing with RabbitMQ, minimizing the programming
overhead often found in other libraries.
rabbitpy is designed to have as simple and pythonic of an API as possible while
still remaining true to RabbitMQ and to the AMQP 0-9-1 specification. There are
two basic ways to interact with rabbitpy, using the simple wrapper methods:
rabbitpy’s simple API methods are meant for one off use, either in your apps or in
the python interpreter. For example, if your application publishes a single
message as part of its lifetime, rabbitpy.publish() should be enough
for almost any publishing concern. However if you are publishing more than
one message, it is not an efficient method to use as it connects and disconnects
from RabbitMQ on each invocation. rabbitpy.get() also connects and
disconnects on each invocation. rabbitpy.consume() does stay connected
as long as you’re iterating through the messages returned by it. Exiting the
generator will close the connection. For a more complete api, see the rabbitpy
core API.
Wrapper methods for easy access to common operations, making them both less
complex and less verbose for one off or simple use cases.
The rabbitpy.simple.Channel class creates a context manager
implementation for use on a single channel where the connection is
automatically created and managed for you.
Create a queue with RabbitMQ. This should only be used for one-off
operations. If a queue name is omitted, the name will be automatically
generated by RabbitMQ.
Publish a message to RabbitMQ. This should only be used for one-off
publishing, as you will suffer a performance penalty if you use it
repeatedly instead creating a connection and channel and publishing on that
While the core rabbitpy API strives to provide an easy to use, Pythonic interface
for RabbitMQ, some developers may prefer a less opinionated AMQP interface. The
rabbitpy.AMQP adapter provides a more traditional AMQP client library
API seen in libraries like pika.
This method acknowledges one or more messages delivered via the Deliver
or Get-Ok methods. The client can ask to confirm a single message or a
set of messages up to and including a specific message.
Parameters:
delivery_tag (int|long) – Server-assigned delivery tag
This method cancels a consumer. This does not affect already delivered
messages, but it does mean the server will not send any more messages
for that consumer. The client may receive an arbitrary number of
messages in between sending the cancel method and receiving the cancel-
ok reply.
This method asks the server to start a “consumer”, which is a transient
request for messages from a specific queue. Consumers last as long as
the channel they were declared on, or until the client cancels them.
This method will act as an generator, returning messages as they are
delivered from the server.
This method provides a direct access to the messages in a queue using a
synchronous dialogue that is designed for specific types of application
where synchronous functionality is more important than performance.
This method allows a client to reject one or more incoming messages. It
can be used to interrupt and cancel large incoming messages, or return
untreatable messages to their original queue. This method is also used
by the server to inform publishers on channels in confirm mode of
unhandled messages. If a publisher receives this method, it probably
needs to republish the offending messages.
Parameters:
delivery_tag (int|long) – Server-assigned delivery tag
This method publishes a message to a specific exchange. The message
will be routed to queues as defined by the exchange configuration and
distributed to any active consumers when the transaction, if any, is
committed.
This method requests a specific quality of service. The QoS can be
specified for the current channel or for all channels on the
connection. The particular properties and semantics of a qos method
always depend on the content class semantics. Though the qos method
could in principle apply to both peers, it is currently meaningful only
for the server.
Parameters:
prefetch_size (int|long) – Prefetch window in octets
prefetch_count (int) – Prefetch window in messages
This method asks the server to redeliver all unacknowledged messages on
a specified channel. Zero or more messages may be redelivered. This
method replaces the asynchronous Recover.
This method allows a client to reject a message. It can be used to
interrupt and cancel large incoming messages, or return untreatable
messages to their original queue.
Parameters:
delivery_tag (int|long) – Server-assigned delivery tag
This method binds a queue to an exchange. Until a queue is bound it
will not receive any messages. In a classic messaging model, store-and-
forward queues are bound to a direct exchange and subscription queues
are bound to a topic exchange.
This method creates or checks a queue. When creating a new queue the
client can specify various properties that control the durability of
the queue and its contents, and the level of sharing for the queue.
This method deletes a queue. When a queue is deleted any pending
messages are sent to a dead-letter queue if this is defined in the
server configuration, and all consumers on the queue are cancelled.
This method commits all message publications and acknowledgments
performed in the current transaction. A new transaction starts
immediately after a commit.
This method abandons all message publications and acknowledgments
performed in the current transaction. A new transaction starts
immediately after a rollback. Note that unacked messages will not be
automatically redelivered by rollback; if that is required an explicit
recover call should be issued.
This method sets the channel to use standard transactions. The client
must use this method at least once on a channel before using the Commit
or Rollback methods.
When they are used as a context manager with the with statement, when your code exits the block, the channel will automatically close, issuing a clean shutdown with RabbitMQ via the Channel.Close RPC request.
You should be aware that if you perform actions on a channel with exchanges, queues, messages or transactions that RabbitMQ does not like, it will close the channel by sending an AMQP Channel.Close RPC request to your application. Upon receipt of such a request, rabbitpy will raise the appropriate exception referenced in the request.
The Channel object is the communications object used by Exchanges,
Messages, Queues, and Transactions. It is created by invoking the
rabbitpy.Connection.channel() method. It can act as a context
manager, allowing for quick shorthand use:
withconnection.channel():# Do something
To create a new channel, invoke
py:meth:~rabbitpy.connection.Connection.channel()
To improve performance, pass blocking_read to True. Note that doing
so prevents KeyboardInterrupt/CTRL-C from exiting the Python
interpreter.
Parameters:
channel_id (int) – The channel # to use for this instance
server_capabilities (dict) – Features the server supports
Turn on Publisher Confirms. If confirms are turned on, the
Message.publish command will return a bool indicating if a message has
been successfully published.
rabbitpy Connection objects are used to connect to RabbitMQ. They provide a thread-safe connection to RabbitMQ that is used to authenticate and send all channel based RPC commands over. Connections use AMQP URI syntax for specifying the all of the connection information, including any connection negotiation options, such as the heartbeat interval. For more information on the various query parameters that can be specified, see the official documentation.
A Connection is a normal python object that you use:
or it can be used as a Python context manager (See PEP 0343):
withrabbitpy.Connection()asconn:# Foo
When it is used as a context manager with the with statement, when your code exits the block, the connection will automatically close.
If RabbitMQ remotely closes your connection via the AMQP Connection.Close RPC request, rabbitpy will raise the appropriate exception referenced in the request.
If heartbeats are enabled (default: 5 minutes) and RabbitMQ does not send a heartbeat request in >= 2 heartbeat intervals, a ConnectionResetException will be raised.
The Connection object is responsible for negotiating a connection and
managing its state. When creating a new instance of the Connection object,
if no URL is passed in, it uses the default connection parameters of
localhost port 5672, virtual host / with the guest/guest username/password
combination. Represented as a AMQP URL the connection information is:
amqp://guest:guest@localhost:5672/%2F
To use a different connection, pass in a AMQP URL that follows the standard
format:
The following example connects to the test virtual host on a RabbitMQ
server running at 192.168.1.200 port 5672 as the user “www” and the
password rabbitmq:
amqp://admin192.168.1.200:5672/test
Note
You should be aware that most connection exceptions may be raised
during the use of all functionality in the library.
Indicates if the connection is blocked from publishing by RabbitMQ.
This flag indicates communication from RabbitMQ that the connection is
blocked using the Connection.Blocked RPC notification from RabbitMQ
that was added in RabbitMQ 3.2.
If blocking_read is True, the cross-thread Queue.get use will use
blocking operations that lower resource utilization and increase
throughput. However, due to how Python’s blocking Queue.get is
implemented, KeyboardInterrupt is not raised when CTRL-C is
pressed.
Parameters:
blocking_read (bool) – Enable for higher throughput
rabbitpy contains two types of exceptions, exceptions that are specific to rabbitpy and exceptions that are raised as a result of a Channel or Connection closure from RabbitMQ. These exceptions will be raised to let you know when you have performed an action like redeclared a pre-existing queue with different values. Consider the following example:
>>> importrabbitpy>>>>>> withrabbitpy.Connection()asconnection:... withconnection.channel()aschannel:... queue=rabbitpy.Queue(channel,'exception-test')... queue.durable=True... queue.declare()... queue.durable=False... queue.declare()...Traceback (most recent call last):
File "<stdin>", line 7, in <module>
File "rabbitpy/connection.py", line 131, in __exit__self._shutdown_connection()
File "rabbitpy/connection.py", line 469, in _shutdown_connectionself._channels[chan_id].close()
File "rabbitpy/channel.py", line 124, in closesuper(Channel,self).close()
File "rabbitpy/base.py", line 185, in closeself.rpc(frame_value)
File "rabbitpy/base.py", line 199, in rpcself._write_frame(frame_value)
File "rabbitpy/base.py", line 311, in _write_frameraiseexceptionrabbitpy.exceptions.AMQPPreconditionFailed: <pamqp.specification.Channel.Close object at 0x10e86bd50>
In this example, the channel that was created on the second line was closed and RabbitMQ is raising the AMQPPreconditionFailed exception via RPC sent to your application using the AMQP Channel.Close method.
Exceptions that may be raised by rabbitpy during use¶
The client sent an invalid sequence of frames, attempting to perform an
operation that was considered invalid by the server. This usually implies a
programming error in the client.
The server could not complete the method because of an internal error. The
server may require intervention by an operator in order to resume normal
operations.
When the exchange cannot deliver to a consumer when the immediate flag is
set. As a result of pending data on the queue or the absence of any
consumers of the queue.
The server could not complete the method because it lacked sufficient
resources. This may be due to the client creating too many of some type of
entity.
The peer sent a frame that was not expected, usually in the context of a
content header and body. This strongly indicates a fault in the peer’s
content processing.
Raised when an action is taken on a Rabbitpy object that is not
supported due to the state of the object. An example would be trying to
ack a Message object when the message object was locally created and not
sent by RabbitMQ via an AMQP Basic.Get or Basic.Consume.
Raised when Rabbitpy can not connect to the specified server and if
a connection fails and the RabbitMQ version does not support the
authentication_failure_close feature added in RabbitMQ 3.2.
Raised if the socket level connection was reset. This can happen due
to the loss of network connection or socket timeout, or more than 2
missed heartbeat intervals if heartbeats are enabled.
Raised if an application attempts to create a channel, exceeding the
maximum number of channels (MAXINT or 2,147,483,647) available for a
single connection. Note that each time a channel object is created, it will
take a new channel id. If you create and destroy 2,147,483,648 channels,
this exception will be raised.
The Exchange class is used to work with RabbitMQ exchanges on an open channel. The following example shows how you can create an exchange using the rabbitpy.Exchange class.
The Message class is used to create messages that you intend to publish to RabbitMQ and is created when a message is received by RabbitMQ by a consumer or as the result of a Queue.get() request.
class rabbitpy.Message(channel, body_value, properties=None, auto_id=False, opinionated=False)[source]¶
Created by both rabbitpy internally when a message is delivered or
returned from RabbitMQ and by implementing applications, the Message class
is used to publish a message to and access and respond to a message from
RabbitMQ.
When specifying properties for a message, pass in a dict of key value items
that match the AMQP Basic.Properties specification with a small caveat.
Due to an overlap in the AMQP specification and the Python keyword
type, the type property is referred to as
message_type.
The following is a list of the available properties:
app_id
content_type
content_encoding
correlation_id
delivery_mode
expiration
headers
message_id
message_type
priority
reply_to
timestamp
user_id
Automated features
When passing in the body value, if it is a dict or list, it will
automatically be JSON serialized and the content type application/json
will be set on the message properties.
When publishing a message to RabbitMQ, if the opinionated value is True
and no message_id value was passed in as a property, a UUID will be
generated and specified as a property of the message.
Additionally, if opinionated is True and the timestamp property
is not specified when passing in properties, the current Unix epoch
value will be set in the message properties.
Note
As of 0.21.0 auto_id is deprecated in favor of
opinionated and it will be removed in a future version. As of
0.22.0 opinionated is defaulted to False.
Parameters:
channel (rabbitpy.channel.Channel) – The channel object for the message object to act upon
body_value (str|bytes|unicode|memoryview|dict|json) – The message body
properties (dict) – A dictionary of message properties
auto_id (bool) – Add a message id if no properties were passed in.
opinionated (bool) – Automatically populate properties if True
Raises:
KeyError – Raised when an invalid property is passed in
Publish the message to the exchange with the specified routing
key.
In Python 2 if the message is a unicode value it will be converted
to a str using str.encode('UTF-8'). If you do not want the
auto-conversion to take place, set the body to a str or bytes
value prior to publishing.
In Python 3 if the message is a str value it will be converted to
a bytes value using bytes(value.encode('UTF-8')). If you do
not want the auto-conversion to take place, set the body to a
bytes value prior to publishing.
Parameters:
exchange (str or rabbitpy.Exchange) – The exchange to publish the message to
The Queue class is used to work with RabbitMQ queues on an open channel. The following example shows how you can create a queue using the Queue.declare method.
If you use either the Queue as an iterator method or Queue.consume() method of consuming messages in PyPy,
you must manually invoke Queue.stop_consuming(). This is due to PyPy not predictably cleaning up after the generator
used for allowing the iteration over messages. Should your code want to test to see if the code is being executed in PyPy,
you can evaluate the boolean rabbitpy.PYPY constant value.
You can use this method instead of the queue object as an iterator
if you need to alter the prefect count, set the consumer priority or
consume in no_ack mode.
New in version 0.26.
Warning
You should only use a single Queue
instance per channel when consuming messages. Failure to do so can
have unintended consequences.
This method is deprecated in favor of
Queue.consume() and will be removed in future releases.
Deprecated since version 0.26.
You can use this message instead of the queue object as an iterator
if you need to alter the prefect count, set the consumer priority or
consume in no_ack mode.
Declare the queue on the RabbitMQ channel passed into the
constructor, returning the current message count for the queue and
its consumer count as a tuple.
Parameters:
passive (bool) – Passive declare to retrieve message count and
consumer count information
Declare a the queue as highly available, passing in a list of nodes
the queue should live on. If no nodes are passed, the queue will be
declared across all nodes in the cluster.
Parameters:
nodes (list) – A list of nodes to declare. If left empty, queue
will be declared on all cluster nodes.
The Tx or transaction class implements transactional functionality with RabbitMQ and allows for any AMQP command to be issued, then committed or rolled back.
The Tx class allows publish and ack operations to be batched into atomic
units of work. The intention is that all publish and ack requests issued
within a transaction will complete successfully or none of them will.
Servers SHOULD implement atomic transactions at least where all publish or
ack requests affect a single queue. Transactions that cover multiple
queues may be non-atomic, given that queues can be created and destroyed
asynchronously, and such events do not form part of any transaction.
Further, the behaviour of transactions with respect to the immediate and
mandatory flags on Basic.Publish methods is not defined.
Parameters:
channel (rabbitpy.channel.Channel) – The channel object to start the transaction on
This method commits all message publications and acknowledgments
performed in the current transaction. A new transaction starts
immediately after a commit.
This method abandons all message publications and acknowledgments
performed in the current transaction. A new transaction starts
immediately after a rollback. Note that unacked messages will not be
automatically redelivered by rollback; if that is required an explicit
recover call should be issued.
This method sets the channel to use standard transactions. The client
must use this method at least once on a channel before using the Commit
or Rollback methods.
The following example will subscribe to a queue named “example” and consume messages
until CTRL-C is pressed:
importrabbitpywithrabbitpy.Connection('amqp://guest:guest@localhost:5672/%2f')asconn:withconn.channel()aschannel:queue=rabbitpy.Queue(channel,'example')# Exit on CTRL-Ctry:# Consume the messageformessageinqueue:message.pprint(True)message.ack()exceptKeyboardInterrupt:print'Exited consumer'
The following example will get a single message at a time from the “example” queue
as long as there are messages in it. It uses len(queue) to check the current
queue depth while it is looping:
importrabbitpywithrabbitpy.Connection('amqp://guest:guest@localhost:5672/%2f')asconn:withconn.channel()aschannel:queue=rabbitpy.Queue(channel,'example')whilelen(queue)>0:message=queue.get()message.pprint(True)message.ack()print('There are {} more messages in the queue'.format(len(queue)))
The following example uses RabbitMQ’s Publisher Confirms feature to allow for validation
that the message was successfully published:
importrabbitpy# Connect to RabbitMQ on localhost, port 5672 as guest/guestwithrabbitpy.Connection('amqp://guest:guest@localhost:5672/%2f')asconn:# Open the channel to communicate with RabbitMQwithconn.channel()aschannel:# Turn on publisher confirmationschannel.enable_publisher_confirms()# Create the message to publishmessage=rabbitpy.Message(channel,'message body value')# Publish the message, looking for the return value to be a bool True/Falseifmessage.publish('test_exchange','test-routing-key',mandatory=True):print'Message publish confirmed by RabbitMQ'else:print'RabbitMQ indicates message publishing failure'
The following example uses RabbitMQ’s Transactions feature to send the message,
then roll it back:
importrabbitpy# Connect to RabbitMQ on localhost, port 5672 as guest/guestwithrabbitpy.Connection('amqp://guest:guest@localhost:5672/%2f')asconn:# Open the channel to communicate with RabbitMQwithconn.channel()aschannel:# Start the transactiontx=rabbitpy.Tx(channel)tx.select()# Create the message to publish & publish itmessage=rabbitpy.Message(channel,'message body value')message.publish('test_exchange','test-routing-key')# Rollback the transactiontx.rollback()
PK TBO)N rabbitpy-latest/objects.inv# Sphinx inventory version 2
# Project: rabbitpy
# Version: 2
# The remainder of this file is compressed using zlib.
xڵQs8^-mi^ୱmzI qj϶$KB zMufeXF
)Oae:g7H%F6њ($y9L_$'
@!qR2{Ce+`$%[8(&{xN8uv]-܊wE:=*ݟ)pQzcNuUT h N@uyg)NζXR>BP=3>dkFs1go%M8(HBc<"Ϻ}3E6~Ӻ'oSR?rjOc53w(ЕB]A!,ᚾ%H_yI75)hbu2lglƾv(V>7 x*
AG*^+csQޝq:0hU/8?ֈ3;K+ؔic7Ͳ)FcaݗG! qȫ?H=(Ǫ<)
]ǒ3o坼T=cLݛ(J>vq<'y`J:РsMvS,]Q:סz$@:Lc=AO1EX.%F?E;* W(˳co\&`E!$*Usm
OEYsVO3fʷ+~rUbS̭cHN'#H ~{M/33B۾3IWO3]
0;mp_PϳYJ
|z Zǟ/X:鮬
'}*w,)FmYW6V_C'f;ٌB^ ؑԷCO5c1ѧF]h1LYVg~>C1D&ͥ;}Ih=̨-WON^g տAFU+itQ';fluqֹn(fYf
L~1!k+mLMΖst˪aNJ/H+(/|j b"vPK TBOkGc rabbitpy-latest/.buildinfo# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: 01696e1003d006aa15aecf66acc590c9
tags: 0957a7f5604f7fa265ade309e7b795c2
PK KBOc c % rabbitpy-latest/_static/websupport.js/*
* websupport.js
* ~~~~~~~~~~~~~
*
* sphinx.websupport utilities for all documentation.
*
* :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
(function($) {
$.fn.autogrow = function() {
return this.each(function() {
var textarea = this;
$.fn.autogrow.resize(textarea);
$(textarea)
.focus(function() {
textarea.interval = setInterval(function() {
$.fn.autogrow.resize(textarea);
}, 500);
})
.blur(function() {
clearInterval(textarea.interval);
});
});
};
$.fn.autogrow.resize = function(textarea) {
var lineHeight = parseInt($(textarea).css('line-height'), 10);
var lines = textarea.value.split('\n');
var columns = textarea.cols;
var lineCount = 0;
$.each(lines, function() {
lineCount += Math.ceil(this.length / columns) || 1;
});
var height = lineHeight * (lineCount + 1);
$(textarea).css('height', height);
};
})(jQuery);
(function($) {
var comp, by;
function init() {
initEvents();
initComparator();
}
function initEvents() {
$(document).on("click", 'a.comment-close', function(event) {
event.preventDefault();
hide($(this).attr('id').substring(2));
});
$(document).on("click", 'a.vote', function(event) {
event.preventDefault();
handleVote($(this));
});
$(document).on("click", 'a.reply', function(event) {
event.preventDefault();
openReply($(this).attr('id').substring(2));
});
$(document).on("click", 'a.close-reply', function(event) {
event.preventDefault();
closeReply($(this).attr('id').substring(2));
});
$(document).on("click", 'a.sort-option', function(event) {
event.preventDefault();
handleReSort($(this));
});
$(document).on("click", 'a.show-proposal', function(event) {
event.preventDefault();
showProposal($(this).attr('id').substring(2));
});
$(document).on("click", 'a.hide-proposal', function(event) {
event.preventDefault();
hideProposal($(this).attr('id').substring(2));
});
$(document).on("click", 'a.show-propose-change', function(event) {
event.preventDefault();
showProposeChange($(this).attr('id').substring(2));
});
$(document).on("click", 'a.hide-propose-change', function(event) {
event.preventDefault();
hideProposeChange($(this).attr('id').substring(2));
});
$(document).on("click", 'a.accept-comment', function(event) {
event.preventDefault();
acceptComment($(this).attr('id').substring(2));
});
$(document).on("click", 'a.delete-comment', function(event) {
event.preventDefault();
deleteComment($(this).attr('id').substring(2));
});
$(document).on("click", 'a.comment-markup', function(event) {
event.preventDefault();
toggleCommentMarkupBox($(this).attr('id').substring(2));
});
}
/**
* Set comp, which is a comparator function used for sorting and
* inserting comments into the list.
*/
function setComparator() {
// If the first three letters are "asc", sort in ascending order
// and remove the prefix.
if (by.substring(0,3) == 'asc') {
var i = by.substring(3);
comp = function(a, b) { return a[i] - b[i]; };
} else {
// Otherwise sort in descending order.
comp = function(a, b) { return b[by] - a[by]; };
}
// Reset link styles and format the selected sort option.
$('a.sel').attr('href', '#').removeClass('sel');
$('a.by' + by).removeAttr('href').addClass('sel');
}
/**
* Create a comp function. If the user has preferences stored in
* the sortBy cookie, use those, otherwise use the default.
*/
function initComparator() {
by = 'rating'; // Default to sort by rating.
// If the sortBy cookie is set, use that instead.
if (document.cookie.length > 0) {
var start = document.cookie.indexOf('sortBy=');
if (start != -1) {
start = start + 7;
var end = document.cookie.indexOf(";", start);
if (end == -1) {
end = document.cookie.length;
by = unescape(document.cookie.substring(start, end));
}
}
}
setComparator();
}
/**
* Show a comment div.
*/
function show(id) {
$('#ao' + id).hide();
$('#ah' + id).show();
var context = $.extend({id: id}, opts);
var popup = $(renderTemplate(popupTemplate, context)).hide();
popup.find('textarea[name="proposal"]').hide();
popup.find('a.by' + by).addClass('sel');
var form = popup.find('#cf' + id);
form.submit(function(event) {
event.preventDefault();
addComment(form);
});
$('#s' + id).after(popup);
popup.slideDown('fast', function() {
getComments(id);
});
}
/**
* Hide a comment div.
*/
function hide(id) {
$('#ah' + id).hide();
$('#ao' + id).show();
var div = $('#sc' + id);
div.slideUp('fast', function() {
div.remove();
});
}
/**
* Perform an ajax request to get comments for a node
* and insert the comments into the comments tree.
*/
function getComments(id) {
$.ajax({
type: 'GET',
url: opts.getCommentsURL,
data: {node: id},
success: function(data, textStatus, request) {
var ul = $('#cl' + id);
var speed = 100;
$('#cf' + id)
.find('textarea[name="proposal"]')
.data('source', data.source);
if (data.comments.length === 0) {
ul.html('
No comments yet.
');
ul.data('empty', true);
} else {
// If there are comments, sort them and put them in the list.
var comments = sortComments(data.comments);
speed = data.comments.length * 100;
appendComments(comments, ul);
ul.data('empty', false);
}
$('#cn' + id).slideUp(speed + 200);
ul.slideDown(speed);
},
error: function(request, textStatus, error) {
showError('Oops, there was a problem retrieving the comments.');
},
dataType: 'json'
});
}
/**
* Add a comment via ajax and insert the comment into the comment tree.
*/
function addComment(form) {
var node_id = form.find('input[name="node"]').val();
var parent_id = form.find('input[name="parent"]').val();
var text = form.find('textarea[name="comment"]').val();
var proposal = form.find('textarea[name="proposal"]').val();
if (text == '') {
showError('Please enter a comment.');
return;
}
// Disable the form that is being submitted.
form.find('textarea,input').attr('disabled', 'disabled');
// Send the comment to the server.
$.ajax({
type: "POST",
url: opts.addCommentURL,
dataType: 'json',
data: {
node: node_id,
parent: parent_id,
text: text,
proposal: proposal
},
success: function(data, textStatus, error) {
// Reset the form.
if (node_id) {
hideProposeChange(node_id);
}
form.find('textarea')
.val('')
.add(form.find('input'))
.removeAttr('disabled');
var ul = $('#cl' + (node_id || parent_id));
if (ul.data('empty')) {
$(ul).empty();
ul.data('empty', false);
}
insertComment(data.comment);
var ao = $('#ao' + node_id);
ao.find('img').attr({'src': opts.commentBrightImage});
if (node_id) {
// if this was a "root" comment, remove the commenting box
// (the user can get it back by reopening the comment popup)
$('#ca' + node_id).slideUp();
}
},
error: function(request, textStatus, error) {
form.find('textarea,input').removeAttr('disabled');
showError('Oops, there was a problem adding the comment.');
}
});
}
/**
* Recursively append comments to the main comment list and children
* lists, creating the comment tree.
*/
function appendComments(comments, ul) {
$.each(comments, function() {
var div = createCommentDiv(this);
ul.append($(document.createElement('li')).html(div));
appendComments(this.children, div.find('ul.comment-children'));
// To avoid stagnating data, don't store the comments children in data.
this.children = null;
div.data('comment', this);
});
}
/**
* After adding a new comment, it must be inserted in the correct
* location in the comment tree.
*/
function insertComment(comment) {
var div = createCommentDiv(comment);
// To avoid stagnating data, don't store the comments children in data.
comment.children = null;
div.data('comment', comment);
var ul = $('#cl' + (comment.node || comment.parent));
var siblings = getChildren(ul);
var li = $(document.createElement('li'));
li.hide();
// Determine where in the parents children list to insert this comment.
for(var i=0; i < siblings.length; i++) {
if (comp(comment, siblings[i]) <= 0) {
$('#cd' + siblings[i].id)
.parent()
.before(li.html(div));
li.slideDown('fast');
return;
}
}
// If we get here, this comment rates lower than all the others,
// or it is the only comment in the list.
ul.append(li.html(div));
li.slideDown('fast');
}
function acceptComment(id) {
$.ajax({
type: 'POST',
url: opts.acceptCommentURL,
data: {id: id},
success: function(data, textStatus, request) {
$('#cm' + id).fadeOut('fast');
$('#cd' + id).removeClass('moderate');
},
error: function(request, textStatus, error) {
showError('Oops, there was a problem accepting the comment.');
}
});
}
function deleteComment(id) {
$.ajax({
type: 'POST',
url: opts.deleteCommentURL,
data: {id: id},
success: function(data, textStatus, request) {
var div = $('#cd' + id);
if (data == 'delete') {
// Moderator mode: remove the comment and all children immediately
div.slideUp('fast', function() {
div.remove();
});
return;
}
// User mode: only mark the comment as deleted
div
.find('span.user-id:first')
.text('[deleted]').end()
.find('div.comment-text:first')
.text('[deleted]').end()
.find('#cm' + id + ', #dc' + id + ', #ac' + id + ', #rc' + id +
', #sp' + id + ', #hp' + id + ', #cr' + id + ', #rl' + id)
.remove();
var comment = div.data('comment');
comment.username = '[deleted]';
comment.text = '[deleted]';
div.data('comment', comment);
},
error: function(request, textStatus, error) {
showError('Oops, there was a problem deleting the comment.');
}
});
}
function showProposal(id) {
$('#sp' + id).hide();
$('#hp' + id).show();
$('#pr' + id).slideDown('fast');
}
function hideProposal(id) {
$('#hp' + id).hide();
$('#sp' + id).show();
$('#pr' + id).slideUp('fast');
}
function showProposeChange(id) {
$('#pc' + id).hide();
$('#hc' + id).show();
var textarea = $('#pt' + id);
textarea.val(textarea.data('source'));
$.fn.autogrow.resize(textarea[0]);
textarea.slideDown('fast');
}
function hideProposeChange(id) {
$('#hc' + id).hide();
$('#pc' + id).show();
var textarea = $('#pt' + id);
textarea.val('').removeAttr('disabled');
textarea.slideUp('fast');
}
function toggleCommentMarkupBox(id) {
$('#mb' + id).toggle();
}
/** Handle when the user clicks on a sort by link. */
function handleReSort(link) {
var classes = link.attr('class').split(/\s+/);
for (var i=0; iThank you! Your comment will show up '
+ 'once it is has been approved by a moderator.');
}
// Prettify the comment rating.
comment.pretty_rating = comment.rating + ' point' +
(comment.rating == 1 ? '' : 's');
// Make a class (for displaying not yet moderated comments differently)
comment.css_class = comment.displayed ? '' : ' moderate';
// Create a div for this comment.
var context = $.extend({}, opts, comment);
var div = $(renderTemplate(commentTemplate, context));
// If the user has voted on this comment, highlight the correct arrow.
if (comment.vote) {
var direction = (comment.vote == 1) ? 'u' : 'd';
div.find('#' + direction + 'v' + comment.id).hide();
div.find('#' + direction + 'u' + comment.id).show();
}
if (opts.moderator || comment.text != '[deleted]') {
div.find('a.reply').show();
if (comment.proposal_diff)
div.find('#sp' + comment.id).show();
if (opts.moderator && !comment.displayed)
div.find('#cm' + comment.id).show();
if (opts.moderator || (opts.username == comment.username))
div.find('#dc' + comment.id).show();
}
return div;
}
/**
* A simple template renderer. Placeholders such as <%id%> are replaced
* by context['id'] with items being escaped. Placeholders such as <#id#>
* are not escaped.
*/
function renderTemplate(template, context) {
var esc = $(document.createElement('div'));
function handle(ph, escape) {
var cur = context;
$.each(ph.split('.'), function() {
cur = cur[this];
});
return escape ? esc.text(cur || "").html() : cur;
}
return template.replace(/<([%#])([\w\.]*)\1>/g, function() {
return handle(arguments[2], arguments[1] == '%' ? true : false);
});
}
/** Flash an error message briefly. */
function showError(message) {
$(document.createElement('div')).attr({'class': 'popup-error'})
.append($(document.createElement('div'))
.attr({'class': 'error-message'}).text(message))
.appendTo('body')
.fadeIn("slow")
.delay(2000)
.fadeOut("slow");
}
/** Add a link the user uses to open the comments popup. */
$.fn.comment = function() {
return this.each(function() {
var id = $(this).attr('id').substring(1);
var count = COMMENT_METADATA[id];
var title = count + ' comment' + (count == 1 ? '' : 's');
var image = count > 0 ? opts.commentBrightImage : opts.commentImage;
var addcls = count == 0 ? ' nocomment' : '';
$(this)
.append(
$(document.createElement('a')).attr({
href: '#',
'class': 'sphinx-comment-open' + addcls,
id: 'ao' + id
})
.append($(document.createElement('img')).attr({
src: image,
alt: 'comment',
title: title
}))
.click(function(event) {
event.preventDefault();
show($(this).attr('id').substring(2));
})
)
.append(
$(document.createElement('a')).attr({
href: '#',
'class': 'sphinx-comment-close hidden',
id: 'ah' + id
})
.append($(document.createElement('img')).attr({
src: opts.closeCommentImage,
alt: 'close',
title: 'close'
}))
.click(function(event) {
event.preventDefault();
hide($(this).attr('id').substring(2));
})
);
});
};
var opts = {
processVoteURL: '/_process_vote',
addCommentURL: '/_add_comment',
getCommentsURL: '/_get_comments',
acceptCommentURL: '/_accept_comment',
deleteCommentURL: '/_delete_comment',
commentImage: '/static/_static/comment.png',
closeCommentImage: '/static/_static/comment-close.png',
loadingImage: '/static/_static/ajax-loader.gif',
commentBrightImage: '/static/_static/comment-bright.png',
upArrow: '/static/_static/up.png',
downArrow: '/static/_static/down.png',
upArrowPressed: '/static/_static/up-pressed.png',
downArrowPressed: '/static/_static/down-pressed.png',
voting: false,
moderator: false
};
if (typeof COMMENT_OPTIONS != "undefined") {
opts = jQuery.extend(opts, COMMENT_OPTIONS);
}
var popupTemplate = '\
\ Sort by:\ best rated\ newest\ oldest\
\\
Add a comment\ (markup):
\``code``
, \ code blocks:::
and an indented block after blank line