2010-07-22 14:19:46 +00:00
|
|
|
#############################################
|
2010-06-23 10:08:39 +00:00
|
|
|
kombu - AMQP Messaging Framework for Python
|
2010-07-22 14:19:46 +00:00
|
|
|
#############################################
|
2010-06-23 10:08:39 +00:00
|
|
|
|
2010-11-10 09:40:45 +00:00
|
|
|
:Version: 0.9.3
|
2010-06-23 10:08:39 +00:00
|
|
|
|
|
|
|
**THIS IS A REWRITE OF CARROT**
|
|
|
|
|
2010-07-22 14:19:46 +00:00
|
|
|
Carrot will be discontinued in favor of Kombu.
|
|
|
|
|
2010-10-27 08:34:28 +00:00
|
|
|
**ORIGINAL CARROT README BELOW**
|
|
|
|
|
|
|
|
Introduction
|
|
|
|
------------
|
|
|
|
|
|
|
|
`kombu` is an `AMQP`_ messaging queue framework. AMQP is the Advanced Message
|
|
|
|
Queuing Protocol, an open standard protocol for message orientation, queuing,
|
|
|
|
routing, reliability and security.
|
|
|
|
|
|
|
|
The aim of `kombu` is to make messaging in Python as easy as possible by
|
|
|
|
providing a high-level interface for producing and consuming messages. At the
|
|
|
|
same time it is a goal to re-use what is already available as much as possible.
|
|
|
|
|
|
|
|
`kombu` has pluggable messaging transports, so it is possible to support
|
|
|
|
several messaging systems. Currently, there is support for `AMQP`_
|
|
|
|
(`py-amqplib`_, `pika`_), `STOMP`_ (`stompy`_). There's also an
|
|
|
|
in-memory transport for testing purposes, using the `Python queue module`_.
|
|
|
|
|
|
|
|
Several AMQP message broker implementations exists, including `RabbitMQ`_,
|
|
|
|
`Apache ActiveMQ`_. You'll need to have one of these installed,
|
|
|
|
personally we've been using `RabbitMQ`_.
|
|
|
|
|
|
|
|
Before you start playing with `kombu`, you should probably read up on
|
|
|
|
AMQP, and you could start with the excellent article about using RabbitMQ
|
|
|
|
under Python, `Rabbits and warrens`_. For more detailed information, you can
|
|
|
|
refer to the `Wikipedia article about AMQP`_.
|
|
|
|
|
|
|
|
.. _`RabbitMQ`: http://www.rabbitmq.com/
|
|
|
|
.. _`AMQP`: http://amqp.org
|
|
|
|
.. _`STOMP`: http://stomp.codehaus.org
|
|
|
|
.. _`stompy`: http://pypi.python.org/stompy
|
|
|
|
.. _`Python Queue module`: http://docs.python.org/library/queue.html
|
|
|
|
.. _`Apache ActiveMQ`: http://activemq.apache.org/
|
|
|
|
.. _`Django`: http://www.djangoproject.com/
|
|
|
|
.. _`Rabbits and warrens`: http://blogs.digitar.com/jjww/2009/01/rabbits-and-warrens/
|
|
|
|
.. _`py-amqplib`: http://barryp.org/software/py-amqplib/
|
|
|
|
.. _`pika`: http://github.com/tonyg/pika
|
|
|
|
.. _`Wikipedia article about AMQP`: http://en.wikipedia.org/wiki/AMQP
|
2010-06-23 10:10:02 +00:00
|
|
|
|
2010-10-27 08:34:28 +00:00
|
|
|
Documentation
|
|
|
|
-------------
|
|
|
|
|
|
|
|
Kombu is using Sphinx, and the latest documentation is available at GitHub:
|
|
|
|
|
|
|
|
http://ask.github.com/kombu
|
|
|
|
|
|
|
|
Quick overview
|
|
|
|
--------------
|
|
|
|
|
|
|
|
.. code-block:: python
|
2010-10-27 12:22:29 +00:00
|
|
|
|
2010-07-20 13:00:17 +00:00
|
|
|
from kombu.connection BrokerConnection
|
2010-08-01 09:41:03 +00:00
|
|
|
from kombu.messaging import Exchange, Queue, Consumer, Producer
|
2010-06-23 10:10:02 +00:00
|
|
|
|
|
|
|
media_exchange = Exchange("media", "direct", durable=True)
|
2010-08-01 09:41:03 +00:00
|
|
|
video_queue = Queue("video", exchange=media_exchange, key="video")
|
2010-06-23 10:10:02 +00:00
|
|
|
|
|
|
|
# connections/channels
|
2010-07-20 13:00:17 +00:00
|
|
|
connection = BrokerConnection("localhost", "guest", "guest", "/")
|
2010-06-23 10:10:02 +00:00
|
|
|
channel = connection.channel()
|
|
|
|
|
|
|
|
# produce
|
|
|
|
producer = Producer(channel, exchange=media_exchange, serializer="json")
|
|
|
|
producer.publish({"name": "/tmp/lolcat1.avi", "size": 1301013})
|
|
|
|
|
|
|
|
# consume
|
2010-08-01 09:41:03 +00:00
|
|
|
consumer = Consumer(channel, video_queue)
|
2010-06-23 10:10:02 +00:00
|
|
|
consumer.register_callback(process_media)
|
|
|
|
consumer.consume()
|
|
|
|
|
2010-10-27 08:34:28 +00:00
|
|
|
# Process messages on all channels
|
2010-06-23 10:10:02 +00:00
|
|
|
while True:
|
|
|
|
connection.drain_events()
|
|
|
|
|
2010-10-27 08:34:28 +00:00
|
|
|
# Consume from several queues on the same channel:
|
2010-08-01 09:41:03 +00:00
|
|
|
video_queue = Queue("video", exchange=media_exchange, key="video")
|
|
|
|
image_queue = Queue("image", exchange=media_exchange, key="image")
|
2010-06-23 10:10:02 +00:00
|
|
|
|
2010-08-01 09:41:03 +00:00
|
|
|
consumer = Consumer(channel, [video_queue, image_queue])
|
2010-07-22 14:09:18 +00:00
|
|
|
consumer.consume()
|
|
|
|
|
|
|
|
while True:
|
|
|
|
connection.drain_events()
|
2010-06-23 10:10:02 +00:00
|
|
|
|
|
|
|
|
2010-10-27 08:34:28 +00:00
|
|
|
`Exchange` and `Queue` are simply declarations that can be pickled
|
|
|
|
and used in configuaration files etc.
|
2010-06-29 15:44:24 +00:00
|
|
|
|
2010-10-27 08:34:28 +00:00
|
|
|
They also support operations, but to do so they need to bound
|
|
|
|
to a channel:
|
|
|
|
|
|
|
|
.. code-block:: python
|
2010-06-29 15:44:24 +00:00
|
|
|
|
|
|
|
>>> exchange = Exchange("tasks", "direct")
|
|
|
|
|
|
|
|
>>> connection = BrokerConnection()
|
|
|
|
>>> channel = connection.channel()
|
2010-07-22 20:10:41 +00:00
|
|
|
>>> bound_exchange = exchange(channel)
|
2010-06-29 15:44:24 +00:00
|
|
|
>>> bound_exchange.delete()
|
|
|
|
|
|
|
|
# the original exchange is not affected, and stays unbound.
|
|
|
|
>>> exchange.delete()
|
|
|
|
raise NotBoundError: Can't call delete on Exchange not bound to
|
|
|
|
a channel.
|
|
|
|
|
2010-06-23 10:08:39 +00:00
|
|
|
Installation
|
|
|
|
============
|
|
|
|
|
2010-10-27 07:17:37 +00:00
|
|
|
You can install `kombu` either via the Python Package Index (PyPI)
|
2010-06-23 10:08:39 +00:00
|
|
|
or from source.
|
|
|
|
|
2010-10-27 07:17:37 +00:00
|
|
|
To install using `pip`,::
|
2010-06-23 10:08:39 +00:00
|
|
|
|
2010-06-29 19:12:29 +00:00
|
|
|
$ pip install kombu
|
2010-06-23 10:08:39 +00:00
|
|
|
|
2010-10-27 07:17:37 +00:00
|
|
|
To install using `easy_install`,::
|
2010-06-23 10:08:39 +00:00
|
|
|
|
2010-06-29 19:12:29 +00:00
|
|
|
$ easy_install kombu
|
2010-06-23 10:08:39 +00:00
|
|
|
|
|
|
|
If you have downloaded a source tarball you can install it
|
|
|
|
by doing the following,::
|
|
|
|
|
|
|
|
$ python setup.py build
|
|
|
|
# python setup.py install # as root
|
|
|
|
|
|
|
|
|
|
|
|
Terminology
|
|
|
|
===========
|
|
|
|
|
|
|
|
There are some concepts you should be familiar with before starting:
|
|
|
|
|
2010-06-29 19:12:29 +00:00
|
|
|
* Producers
|
2010-06-23 10:08:39 +00:00
|
|
|
|
2010-06-29 19:12:29 +00:00
|
|
|
Producers sends messages to an exchange.
|
2010-06-23 10:08:39 +00:00
|
|
|
|
|
|
|
* Exchanges
|
|
|
|
|
|
|
|
Messages are sent to exchanges. Exchanges are named and can be
|
|
|
|
configured to use one of several routing algorithms. The exchange
|
|
|
|
routes the messages to consumers by matching the routing key in the
|
|
|
|
message with the routing key the consumer provides when binding to
|
|
|
|
the exchange.
|
|
|
|
|
|
|
|
* Consumers
|
|
|
|
|
|
|
|
Consumers declares a queue, binds it to a exchange and receives
|
|
|
|
messages from it.
|
|
|
|
|
|
|
|
* Queues
|
|
|
|
|
|
|
|
Queues receive messages sent to exchanges. The queues are declared
|
|
|
|
by consumers.
|
|
|
|
|
|
|
|
* Routing keys
|
|
|
|
|
|
|
|
Every message has a routing key. The interpretation of the routing
|
|
|
|
key depends on the exchange type. There are four default exchange
|
|
|
|
types defined by the AMQP standard, and vendors can define custom
|
|
|
|
types (so see your vendors manual for details).
|
|
|
|
|
|
|
|
These are the default exchange types defined by AMQP/0.8:
|
|
|
|
|
|
|
|
* Direct exchange
|
|
|
|
|
|
|
|
Matches if the routing key property of the message and
|
2010-10-27 07:17:37 +00:00
|
|
|
the `routing_key` attribute of the consumer are identical.
|
2010-06-23 10:08:39 +00:00
|
|
|
|
|
|
|
* Fan-out exchange
|
|
|
|
|
|
|
|
Always matches, even if the binding does not have a routing
|
|
|
|
key.
|
|
|
|
|
|
|
|
* Topic exchange
|
|
|
|
|
|
|
|
Matches the routing key property of the message by a primitive
|
|
|
|
pattern matching scheme. The message routing key then consists
|
2010-10-27 07:17:37 +00:00
|
|
|
of words separated by dots (`"."`, like domain names), and
|
|
|
|
two special characters are available; star (`"*"`) and hash
|
|
|
|
(`"#"`). The star matches any word, and the hash matches
|
|
|
|
zero or more words. For example `"*.stock.#"` matches the
|
|
|
|
routing keys `"usd.stock"` and `"eur.stock.db"` but not
|
|
|
|
`"stock.nasdaq"`.
|
2010-06-23 10:08:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
Examples
|
|
|
|
========
|
|
|
|
|
|
|
|
Creating a connection
|
|
|
|
---------------------
|
|
|
|
|
|
|
|
You can set up a connection by creating an instance of
|
2010-10-27 07:17:37 +00:00
|
|
|
`kombu.BrokerConnection`, with the appropriate options for
|
2010-06-23 10:08:39 +00:00
|
|
|
your broker:
|
|
|
|
|
2010-06-29 19:12:29 +00:00
|
|
|
>>> from kombu import BrokerConnection
|
2010-06-23 10:08:39 +00:00
|
|
|
>>> conn = BrokerConnection(hostname="localhost", port=5672,
|
2010-06-29 19:12:29 +00:00
|
|
|
... userid="guest", password="guest",
|
|
|
|
... virtual_host="/")
|
2010-06-23 10:08:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
Receiving messages using a Consumer
|
|
|
|
-----------------------------------
|
|
|
|
|
|
|
|
First we open up a Python shell and start a message consumer.
|
|
|
|
|
2010-10-27 07:17:37 +00:00
|
|
|
This consumer declares a queue named `"feed"`, receiving messages with
|
|
|
|
the routing key `"importer"` from the `"feed"` exchange.
|
2010-06-23 10:08:39 +00:00
|
|
|
|
2010-08-01 09:41:03 +00:00
|
|
|
>>> from kombu import Exchange, Queue, Consumer
|
2010-06-29 19:12:29 +00:00
|
|
|
|
|
|
|
>>> feed_exchange = Exchange("feed", type="direct")
|
2010-08-01 09:41:03 +00:00
|
|
|
>>> feed_queue = Queue("feed", feed_exchange, "importer")
|
2010-06-29 19:12:29 +00:00
|
|
|
|
|
|
|
>>> channel = connection.channel()
|
2010-08-01 09:41:03 +00:00
|
|
|
>>> consumer = Consumer(channel, [feed_queue])
|
2010-06-23 10:08:39 +00:00
|
|
|
|
|
|
|
>>> def import_feed_callback(message_data, message)
|
|
|
|
... feed_url = message_data["import_feed"]
|
|
|
|
... print("Got feed import message for: %s" % feed_url)
|
|
|
|
... # something importing this feed url
|
|
|
|
... # import_feed(feed_url)
|
|
|
|
... message.ack()
|
2010-06-29 19:12:29 +00:00
|
|
|
|
2010-06-23 10:08:39 +00:00
|
|
|
>>> consumer.register_callback(import_feed_callback)
|
|
|
|
|
2010-06-29 19:12:29 +00:00
|
|
|
>>> # Consume messages in a loop
|
|
|
|
>>> while True:
|
|
|
|
... connection.drain_events(timeout=...)
|
|
|
|
|
|
|
|
Sending messages using a Producer
|
|
|
|
---------------------------------
|
2010-06-23 10:08:39 +00:00
|
|
|
|
|
|
|
Then we open up another Python shell to send some messages to the consumer
|
|
|
|
defined in the last section.
|
|
|
|
|
2010-06-29 19:12:29 +00:00
|
|
|
>>> from kombu import Exchange, Producer
|
|
|
|
>>> feed_exchange = Exchange("feed", type="direct")
|
|
|
|
|
|
|
|
>>> channel = connection.channel()
|
|
|
|
>>> producer = Producer(channel, feed_exchange)
|
|
|
|
>>> producer.publish({"import_feed": "http://cnn.com/rss/edition.rss"},
|
|
|
|
... routing_key="importer")
|
|
|
|
>>> producer.close()
|
2010-06-23 10:08:39 +00:00
|
|
|
|
|
|
|
|
2010-06-29 19:12:29 +00:00
|
|
|
Look in the first Python shell again (where consumer loop is running),
|
2010-06-23 10:08:39 +00:00
|
|
|
where the following text has been printed to the screen::
|
|
|
|
|
2010-07-22 20:10:41 +00:00
|
|
|
Got feed import message for: http://cnn.com/rss/edition.rss
|
2010-06-23 10:08:39 +00:00
|
|
|
|
|
|
|
Serialization of Data
|
|
|
|
-----------------------
|
|
|
|
|
|
|
|
By default every message is encoded using `JSON`_, so sending
|
|
|
|
Python data structures like dictionaries and lists works.
|
2010-10-27 07:17:37 +00:00
|
|
|
`YAML`_, `msgpack`_ and Python's built-in `pickle` module is also supported,
|
2010-06-23 10:08:39 +00:00
|
|
|
and if needed you can register any custom serialization scheme you
|
|
|
|
want to use.
|
|
|
|
|
|
|
|
.. _`JSON`: http://www.json.org/
|
|
|
|
.. _`YAML`: http://yaml.org/
|
|
|
|
.. _`msgpack`: http://msgpack.sourceforge.net/
|
|
|
|
|
|
|
|
Each option has its advantages and disadvantages.
|
|
|
|
|
2010-10-27 07:17:37 +00:00
|
|
|
`json` -- JSON is supported in many programming languages, is now
|
2010-07-22 20:10:41 +00:00
|
|
|
a standard part of Python (since 2.6), and is fairly fast to
|
2010-10-27 07:17:37 +00:00
|
|
|
decode using the modern Python libraries such as `cjson` or
|
|
|
|
`simplejson`.
|
2010-06-29 19:12:29 +00:00
|
|
|
|
2010-10-27 07:17:37 +00:00
|
|
|
The primary disadvantage to `JSON` is that it limits you to
|
2010-07-22 20:10:41 +00:00
|
|
|
the following data types: strings, unicode, floats, boolean,
|
2010-06-23 10:08:39 +00:00
|
|
|
dictionaries, and lists. Decimals and dates are notably missing.
|
2010-06-29 19:12:29 +00:00
|
|
|
|
2010-06-23 10:08:39 +00:00
|
|
|
Also, binary data will be transferred using base64 encoding, which
|
2010-07-22 20:10:41 +00:00
|
|
|
will cause the transferred data to be around 34% larger than an
|
|
|
|
encoding which supports native binary types.
|
2010-06-29 19:12:29 +00:00
|
|
|
|
2010-07-22 20:10:41 +00:00
|
|
|
However, if your data fits inside the above constraints and
|
2010-10-27 07:17:37 +00:00
|
|
|
you need cross-language support, the default setting of `JSON`
|
2010-07-22 20:10:41 +00:00
|
|
|
is probably your best choice.
|
2010-06-29 19:12:29 +00:00
|
|
|
|
2010-10-27 07:17:37 +00:00
|
|
|
`pickle` -- If you have no desire to support any language other than
|
|
|
|
Python, then using the `pickle` encoding will gain you
|
2010-07-22 20:10:41 +00:00
|
|
|
the support of all built-in Python data types (except class instances),
|
2010-06-23 10:08:39 +00:00
|
|
|
smaller messages when sending binary files, and a slight speedup
|
2010-10-27 07:17:37 +00:00
|
|
|
over `JSON` processing.
|
2010-06-23 10:08:39 +00:00
|
|
|
|
2010-10-27 07:17:37 +00:00
|
|
|
`yaml` -- YAML has many of the same characteristics as `json`,
|
2010-07-22 20:10:41 +00:00
|
|
|
except that it natively supports more data types (including dates,
|
2010-06-23 10:08:39 +00:00
|
|
|
recursive references, etc.)
|
2010-06-29 19:12:29 +00:00
|
|
|
|
2010-06-23 10:08:39 +00:00
|
|
|
However, the Python libraries for YAML are a good bit slower
|
2010-07-22 20:10:41 +00:00
|
|
|
than the libraries for JSON.
|
2010-06-29 19:12:29 +00:00
|
|
|
|
2010-06-23 10:08:39 +00:00
|
|
|
If you need a more expressive set of data types and need to maintain
|
2010-10-27 07:17:37 +00:00
|
|
|
cross-language compatibility, then `YAML` may be a better fit
|
2010-07-22 20:10:41 +00:00
|
|
|
than the above.
|
2010-06-23 10:08:39 +00:00
|
|
|
|
2010-07-22 20:10:41 +00:00
|
|
|
To instruct carrot to use an alternate serialization method,
|
2010-06-23 10:08:39 +00:00
|
|
|
use one of the following options.
|
|
|
|
|
2010-06-29 19:12:29 +00:00
|
|
|
1. Set the serialization option on a per-producer basis::
|
2010-06-23 10:08:39 +00:00
|
|
|
|
2010-06-29 19:12:29 +00:00
|
|
|
>>> producer = Producer(channel,
|
|
|
|
... exchange=exchange,
|
|
|
|
... serializer="yaml")
|
2010-06-23 10:08:39 +00:00
|
|
|
|
2010-06-29 19:12:29 +00:00
|
|
|
2. Set the serialization option per message::
|
2010-06-23 10:08:39 +00:00
|
|
|
|
2010-06-29 19:12:29 +00:00
|
|
|
>>> producer.publish(message, routing_key=rkey,
|
|
|
|
... serializer="pickle")
|
2010-06-23 10:08:39 +00:00
|
|
|
|
2010-10-27 07:17:37 +00:00
|
|
|
Note that a `Consumer` do not need the serialization method specified.
|
2010-06-29 19:12:29 +00:00
|
|
|
They can auto-detect the serialization method as the
|
|
|
|
content-type is sent as a message header.
|
2010-06-23 10:08:39 +00:00
|
|
|
|
|
|
|
Sending raw data without Serialization
|
|
|
|
---------------------------------------
|
|
|
|
|
|
|
|
In some cases, you don't need your message data to be serialized. If you
|
|
|
|
pass in a plain string or unicode object as your message, then carrot will
|
|
|
|
not waste cycles serializing/deserializing the data.
|
|
|
|
|
2010-10-27 07:17:37 +00:00
|
|
|
You can optionally specify a `content_type` and `content_encoding`
|
2010-06-23 10:08:39 +00:00
|
|
|
for the raw data:
|
|
|
|
|
2010-06-29 19:12:29 +00:00
|
|
|
>>> producer.send(open('~/my_picture.jpg','rb').read(),
|
|
|
|
content_type="image/jpeg",
|
|
|
|
content_encoding="binary",
|
|
|
|
routing_key=rkey)
|
|
|
|
|
2010-10-27 07:17:37 +00:00
|
|
|
The `Message` object returned by the `Consumer` class will have a
|
|
|
|
`content_type` and `content_encoding` attribute.
|
2010-06-23 10:08:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
Sub-classing the messaging classes
|
|
|
|
----------------------------------
|
|
|
|
|
2010-10-27 07:17:37 +00:00
|
|
|
The `Consumer`, and `Producer` classes can also be sub classed. Thus you
|
2010-06-29 19:12:29 +00:00
|
|
|
can define the above producer and consumer like so:
|
2010-06-23 10:08:39 +00:00
|
|
|
|
2010-06-29 19:12:29 +00:00
|
|
|
>>> class FeedProducer(Producer):
|
|
|
|
... exchange = exchange
|
2010-06-23 10:08:39 +00:00
|
|
|
... routing_key = "importer"
|
|
|
|
...
|
|
|
|
... def import_feed(self, feed_url):
|
2010-06-29 19:12:29 +00:00
|
|
|
... return self.publish({"action": "import_feed",
|
|
|
|
... "feed_url": feed_url})
|
2010-06-23 10:08:39 +00:00
|
|
|
|
|
|
|
>>> class FeedConsumer(Consumer):
|
2010-08-01 09:41:03 +00:00
|
|
|
... queues = queues
|
2010-06-23 10:08:39 +00:00
|
|
|
...
|
|
|
|
... def receive(self, message_data, message):
|
|
|
|
... action = message_data["action"]
|
|
|
|
... if action == "import_feed":
|
|
|
|
... # something importing this feed
|
|
|
|
... # import_feed(message_data["feed_url"])
|
|
|
|
message.ack()
|
|
|
|
... else:
|
|
|
|
... raise Exception("Unknown action: %s" % action)
|
|
|
|
|
2010-06-29 19:12:29 +00:00
|
|
|
>>> producer = FeedProducer(channel)
|
|
|
|
>>> producer.import_feed("http://cnn.com/rss/edition.rss")
|
|
|
|
>>> producer.close()
|
2010-06-23 10:08:39 +00:00
|
|
|
|
2010-06-29 19:12:29 +00:00
|
|
|
>>> consumer = FeedConsumer(channel)
|
|
|
|
>>> while True:
|
|
|
|
... connection.drain_events()
|
2010-06-23 10:08:39 +00:00
|
|
|
|
|
|
|
Getting Help
|
|
|
|
============
|
|
|
|
|
|
|
|
Mailing list
|
|
|
|
------------
|
|
|
|
|
|
|
|
Join the `carrot-users`_ mailing list.
|
|
|
|
|
|
|
|
.. _`carrot-users`: http://groups.google.com/group/carrot-users/
|
|
|
|
|
|
|
|
Bug tracker
|
|
|
|
===========
|
|
|
|
|
|
|
|
If you have any suggestions, bug reports or annoyances please report them
|
2010-06-29 19:12:29 +00:00
|
|
|
to our issue tracker at http://github.com/ask/kombu/issues/
|
2010-06-23 10:08:39 +00:00
|
|
|
|
|
|
|
Contributing
|
|
|
|
============
|
|
|
|
|
2010-10-27 07:17:37 +00:00
|
|
|
Development of `kombu` happens at Github: http://github.com/ask/kombu
|
2010-06-23 10:08:39 +00:00
|
|
|
|
|
|
|
You are highly encouraged to participate in the development. If you don't
|
|
|
|
like Github (for some reason) you're welcome to send regular patches.
|
|
|
|
|
|
|
|
License
|
|
|
|
=======
|
|
|
|
|
2010-10-27 07:17:37 +00:00
|
|
|
This software is licensed under the `New BSD License`. See the :file:`LICENSE`
|
2010-06-23 10:08:39 +00:00
|
|
|
file in the top distribution directory for the full license text.
|