summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJérôme Schneider <jschneider@entrouvert.com>2014-11-01 17:56:02 (GMT)
committerJérôme Schneider <jschneider@entrouvert.com>2014-11-01 17:56:02 (GMT)
commit92d64728d858d695e3e6d68ee243da81955fdd63 (patch)
treeaf5c62c495dbd8ede241a7ec6b4a3a2a93602456
downloadpython-amqp-92d64728d858d695e3e6d68ee243da81955fdd63.zip
python-amqp-92d64728d858d695e3e6d68ee243da81955fdd63.tar.gz
python-amqp-92d64728d858d695e3e6d68ee243da81955fdd63.tar.bz2
Initial import from tarballHEADmaster
-rw-r--r--Changelog484
-rw-r--r--LICENSE458
-rw-r--r--MANIFEST.in6
-rw-r--r--PKG-INFO128
-rw-r--r--README.rst104
-rw-r--r--amqp.egg-info/PKG-INFO128
-rw-r--r--amqp.egg-info/SOURCES.txt67
-rw-r--r--amqp.egg-info/dependency_links.txt1
-rw-r--r--amqp.egg-info/not-zip-safe1
-rw-r--r--amqp.egg-info/top_level.txt1
-rw-r--r--amqp/__init__.py70
-rw-r--r--amqp/abstract_channel.py93
-rw-r--r--amqp/basic_message.py124
-rw-r--r--amqp/channel.py2537
-rw-r--r--amqp/connection.py1004
-rw-r--r--amqp/exceptions.py258
-rw-r--r--amqp/five.py188
-rw-r--r--amqp/method_framing.py231
-rw-r--r--amqp/protocol.py13
-rw-r--r--amqp/serialization.py510
-rw-r--r--amqp/transport.py294
-rw-r--r--amqp/utils.py102
-rwxr-xr-xdemo/amqp_clock.py78
-rwxr-xr-xdemo/demo_receive.py83
-rwxr-xr-xdemo/demo_send.py66
-rw-r--r--docs/.static/.keep0
-rw-r--r--docs/.templates/page.html4
-rw-r--r--docs/.templates/sidebarintro.html4
-rw-r--r--docs/.templates/sidebarlogo.html3
-rw-r--r--docs/Makefile81
-rw-r--r--docs/_ext/applyxrefs.py92
-rw-r--r--docs/_ext/literals_to_xrefs.py173
-rw-r--r--docs/_theme/celery/static/celery.css_t401
-rw-r--r--docs/_theme/celery/theme.conf5
-rw-r--r--docs/changelog.rst484
-rw-r--r--docs/conf.py127
-rw-r--r--docs/includes/intro.txt96
-rw-r--r--docs/index.rst22
-rw-r--r--docs/reference/amqp.abstract_channel.rst11
-rw-r--r--docs/reference/amqp.basic_message.rst11
-rw-r--r--docs/reference/amqp.channel.rst11
-rw-r--r--docs/reference/amqp.connection.rst11
-rw-r--r--docs/reference/amqp.exceptions.rst11
-rw-r--r--docs/reference/amqp.five.rst11
-rw-r--r--docs/reference/amqp.method_framing.rst11
-rw-r--r--docs/reference/amqp.protocol.rst11
-rw-r--r--docs/reference/amqp.serialization.rst11
-rw-r--r--docs/reference/amqp.transport.rst11
-rw-r--r--docs/reference/amqp.utils.rst11
-rw-r--r--docs/reference/index.rst23
-rw-r--r--docs/templates/readme.txt5
-rw-r--r--extra/README10
-rwxr-xr-xextra/generate_skeleton_0_8.py377
-rwxr-xr-xextra/release/bump_version.py181
-rwxr-xr-xextra/release/sphinx-to-rst.py75
-rw-r--r--extra/update_comments_from_spec.py76
-rwxr-xr-xfuntests/run_all.py38
-rw-r--r--funtests/settings.py91
-rwxr-xr-xfuntests/test_basic_message.py132
-rwxr-xr-xfuntests/test_channel.py317
-rwxr-xr-xfuntests/test_connection.py127
-rwxr-xr-xfuntests/test_exceptions.py47
-rwxr-xr-xfuntests/test_serialization.py411
-rw-r--r--funtests/test_with.py70
-rw-r--r--requirements/docs.txt2
-rw-r--r--requirements/pkgutils.txt5
-rw-r--r--requirements/test.txt5
-rw-r--r--setup.cfg5
-rw-r--r--setup.py132
69 files changed, 10771 insertions, 0 deletions
diff --git a/Changelog b/Changelog
new file mode 100644
index 0000000..e036942
--- /dev/null
+++ b/Changelog
@@ -0,0 +1,484 @@
+Changes
+=======
+
+py-amqp is fork of amqplib used by Kombu containing additional features and improvements.
+The previous amqplib changelog is here:
+http://code.google.com/p/py-amqplib/source/browse/CHANGES
+
+.. _version-1.4.5:
+
+1.4.5
+=====
+:release-date: 2014-04-15 09:00 P.M UTC
+:release-by: Ask Solem
+
+- Can now deserialize more AMQP types.
+
+ Now handles types ``short string``, ``short short int``,
+ ``short short unsigned int``, ``short int``, ``short unsigned int``,
+ ``long unsigned int``, ``long long int``, ``long long unsigned int``
+ and ``float`` which for some reason was missing, even in the original
+ amqplib module.
+
+- SSL: Workaround for Python SSL bug.
+
+ A bug in the python socket library causes ``ssl.read/write()``
+ on a closed socket to raise :exc:`AttributeError` instead of
+ :exc:`IOError`.
+
+ Fix contributed by Craig Jellick.
+
+- ``Transport.__del_`` now handles errors occurring at late interpreter
+ shutdown (Issue #36).
+
+.. _version-1.4.4:
+
+1.4.4
+=====
+:release-date: 2014-03-03 04:00 P.M UTC
+:release-by: Ask Solem
+
+- SSL transport accidentally disconnected after read timeout.
+
+ Fix contributed by Craig Jellick.
+
+.. _version-1.4.3:
+
+1.4.3
+=====
+:release-date: 2014-02-09 03:00 P.M UTC
+:release-by: Ask Solem
+
+- Fixed bug where more data was requested from the socket
+ than was actually needed.
+
+ Contributed by Ionel Cristian Mărieș.
+
+.. _version-1.4.2:
+
+1.4.2
+=====
+:release-date: 2014-01-23 05:00 P.M UTC
+
+- Heartbeat negotiation would use heartbeat value from server even
+ if heartbeat disabled (Issue #31).
+
+.. _version-1.4.1:
+
+1.4.1
+=====
+:release-date: 2014-01-14 09:30 P.M UTC
+:release-by: Ask Solem
+
+- Fixed error occurring when heartbeats disabled.
+
+.. _version-1.4.0:
+
+1.4.0
+=====
+:release-date: 2014-01-13 03:00 P.M UTC
+:release-by: Ask Solem
+
+- Heartbeat implementation improved (Issue #6).
+
+ The new heartbeat behavior is the same approach as taken by the
+ RabbitMQ java library.
+
+ This also means that clients should preferably call the ``heartbeat_tick``
+ method more frequently (like every second) instead of using the old
+ ``rate`` argument (which is now ignored).
+
+ - Heartbeat interval is negotiated with the server.
+ - Some delay is allowed if the heartbeat is late.
+ - Monotonic time is used to keep track of the heartbeat
+ instead of relying on the caller to call the checking function
+ at the right time.
+
+ Contributed by Dustin J. Mitchell.
+
+- NoneType is now supported in tables and arrays.
+
+ Contributed by Dominik Fässler.
+
+- SSLTransport: Now handles ``ENOENT``.
+
+ Fix contributed by Adrien Guinet.
+
+.. _version-1.3.3:
+
+1.3.3
+=====
+:release-date: 2013-11-11 03:30 P.M UTC
+:release-by: Ask Solem
+
+- SSLTransport: Now keeps read buffer if an exception is raised
+ (Issue #26).
+
+ Fix contributed by Tommie Gannert.
+
+.. _version-1.3.2:
+
+1.3.2
+=====
+:release-date: 2013-10-29 02:00 P.M UTC
+:release-by: Ask Solem
+
+- Message.channel is now a channel object (not the channel id).
+
+- Bug in previous version caused the socket to be flagged as disconnected
+ at EAGAIN/EINTR.
+
+.. _version-1.3.1:
+
+1.3.1
+=====
+:release-date: 2013-10-24 04:00 P.M UTC
+:release-by: Ask Solem
+
+- Now implements Connection.connected (Issue #22).
+
+- Fixed bug where ``str(AMQPError)`` did not return string.
+
+.. _version-1.3.0:
+
+1.3.0
+=====
+:release-date: 2013-09-04 02:39 P.M UTC
+:release-by: Ask Solem
+
+- Now sets ``Message.channel`` on delivery (Issue #12)
+
+ amqplib used to make the channel object available
+ as ``Message.delivery_info['channel']``, but this was removed
+ in py-amqp. librabbitmq sets ``Message.channel``,
+ which is a more reasonable solution in our opinion as that
+ keeps the delivery info intact.
+
+- New option to wait for publish confirmations (Issue #3)
+
+ There is now a new Connection ``confirm_publish`` that will
+ force any ``basic_publish`` call to wait for confirmation.
+
+ Enabling publisher confirms like this degrades performance
+ considerably, but can be suitable for some applications
+ and now it's possible by configuration.
+
+- ``queue_declare`` now returns named tuple of type
+ :class:`~amqp.protocol.basic_declare_ok_t`.
+
+ Supporting fields: ``queue``, ``message_count``, and
+ ``consumer_count``.
+
+- Contents of ``Channel.returned_messages`` is now named tuples.
+
+ Supporting fields: ``reply_code``, ``reply_text``, ``exchange``,
+ ``routing_key``, and ``message``.
+
+- Sockets now set to close on exec using the ``FD_CLOEXEC`` flag.
+
+ Currently only supported on platforms supporting this flag,
+ which does not include Windows.
+
+ Contributed by Tommie Gannert.
+
+.. _version-1.2.1:
+
+1.2.1
+=====
+:release-date: 2013-08-16 05:30 P.M UTC
+:release-by: Ask Solem
+
+- Adds promise type: :meth:`amqp.utils.promise`
+
+- Merges fixes from 1.0.x
+
+.. _version-1.2.0:
+
+1.2.0
+=====
+:release-date: 2012-11-12 04:00 P.M UTC
+:release-by: Ask Solem
+
+- New exception hierarchy:
+
+ - :class:`~amqp.AMQPError`
+ - :class:`~amqp.ConnectionError`
+ - :class:`~amqp.RecoverableConnectionError`
+ - :class:`~amqp.ConsumerCancelled`
+ - :class:`~amqp.ConnectionForced`
+ - :class:`~amqp.ResourceError`
+ - :class:`~IrrecoverableConnectionError`
+ - :class:`~amqp.ChannelNotOpen`
+ - :class:`~amqp.FrameError`
+ - :class:`~amqp.FrameSyntaxError`
+ - :class:`~amqp.InvalidCommand`
+ - :class:`~amqp.InvalidPath`
+ - :class:`~amqp.NotAllowed`
+ - :class:`~amqp.UnexpectedFrame`
+ - :class:`~amqp.AMQPNotImplementedError`
+ - :class:`~amqp.InternalError`
+ - :class:`~amqp.ChannelError`
+ - :class:`~RecoverableChannelError`
+ - :class:`~amqp.ContentTooLarge`
+ - :class:`~amqp.NoConsumers`
+ - :class:`~amqp.ResourceLocked`
+ - :class:`~IrrecoverableChannelError`
+ - :class:`~amqp.AccessRefused`
+ - :class:`~amqp.NotFound`
+ - :class:`~amqp.PreconditionFailed`
+
+
+.. _version-1.1.0:
+
+1.1.0
+=====
+:release-date: 2012-11-08 10:36 P.M UTC
+:release-by: Ask Solem
+
+- No longer supports Python 2.5
+
+- Fixed receiving of float table values.
+
+- Now Supports Python 3 and Python 2.6+ in the same source code.
+
+- Python 3 related fixes.
+
+.. _version-1.0.13:
+
+1.0.13
+======
+:release-date: 2013-07-31 04:00 P.M BST
+:release-by: Ask Solem
+
+- Fixed problems with the SSL transport (Issue #15).
+
+ Fix contributed by Adrien Guinet.
+
+- Small optimizations
+
+.. _version-1.0.12:
+
+1.0.12
+======
+:release-date: 2013-06-25 02:00 P.M BST
+:release-by: Ask Solem
+
+- Fixed another Python 3 compatibility problem.
+
+.. _version-1.0.11:
+
+1.0.11
+======
+:release-date: 2013-04-11 06:00 P.M BST
+:release-by: Ask Solem
+
+- Fixed Python 3 incompatibility in ``amqp/transport.py``.
+
+.. _version-1.0.10:
+
+1.0.10
+======
+:release-date: 2013-03-21 03:30 P.M UTC
+:release-by: Ask Solem
+
+- Fixed Python 3 incompatibility in ``amqp/serialization.py``.
+ (Issue #11).
+
+.. _version-1.0.9:
+
+1.0.9
+=====
+:release-date: 2013-03-08 10:40 A.M UTC
+:release-by: Ask Solem
+
+- Publisher ack callbacks should now work after typo fix (Issue #9).
+
+- ``channel(explicit_id)`` will now claim that id from the array
+ of unused channel ids.
+
+- Fixes Jython compatibility.
+
+.. _version-1.0.8:
+
+1.0.8
+=====
+:release-date: 2013-02-08 01:00 P.M UTC
+:release-by: Ask Solem
+
+- Fixed SyntaxError on Python 2.5
+
+.. _version-1.0.7:
+
+1.0.7
+=====
+:release-date: 2013-02-08 01:00 P.M UTC
+:release-by: Ask Solem
+
+- Workaround for bug on some Python 2.5 installations where (2**32) is 0.
+
+- Can now serialize the ARRAY type.
+
+ Contributed by Adam Wentz.
+
+- Fixed tuple format bug in exception (Issue #4).
+
+.. _version-1.0.6:
+
+1.0.6
+=====
+:release-date: 2012-11-29 01:14 P.M UTC
+:release-by: Ask Solem
+
+- ``Channel.close`` is now ignored if the connection attribute is None.
+
+.. _version-1.0.5:
+
+1.0.5
+=====
+:release-date: 2012-11-21 04:00 P.M UTC
+:release-by: Ask Solem
+
+- ``Channel.basic_cancel`` is now ignored if the channel was already closed.
+
+- ``Channel.events`` is now a dict of sets::
+
+ >>> channel.events['basic_return'].add(on_basic_return)
+ >>> channel.events['basic_return'].discard(on_basic_return)
+
+.. _version-1.0.4:
+
+1.0.4
+=====
+:release-date: 2012-11-13 04:00 P.M UTC
+:release-by: Ask Solem
+
+- Fixes Python 2.5 support
+
+.. _version-1.0.3:
+
+1.0.3
+=====
+:release-date: 2012-11-12 04:00 P.M UTC
+:release-by: Ask Solem
+
+- Now can also handle float in headers/tables when receiving messages.
+
+- Now uses :class:`array.array` to keep track of unused channel ids.
+
+- The :data:`~amqp.exceptions.METHOD_NAME_MAP` has been updated for
+ amqp/0.9.1 and Rabbit extensions.
+
+- Removed a bunch of accidentally included images.
+
+.. _version-1.0.2:
+
+1.0.2
+=====
+:release-date: 2012-11-06 05:00 P.M UTC
+:release-by: Ask Solem
+
+- Now supports float values in headers/tables.
+
+.. _version-1.0.1:
+
+1.0.1
+=====
+:release-date: 2012-11-05 01:00 P.M UTC
+:release-by: Ask Solem
+
+- Connection errors no longer includes :exc:`AttributeError`.
+
+- Fixed problem with using the SSL transport in a non-blocking context.
+
+ Fix contributed by Mher Movsisyan.
+
+
+.. _version-1.0.0:
+
+1.0.0
+=====
+:release-date: 2012-11-05 01:00 P.M UTC
+:release-by: Ask Solem
+
+- Channels are now restored on channel error, so that the connection does not
+ have to closed.
+
+.. _version-0.9.4:
+
+Version 0.9.4
+=============
+
+- Adds support for ``exchange_bind`` and ``exchange_unbind``.
+
+ Contributed by Rumyana Neykova
+
+- Fixed bugs in funtests and demo scripts.
+
+ Contributed by Rumyana Neykova
+
+.. _version-0.9.3:
+
+Version 0.9.3
+=============
+
+- Fixed bug that could cause the consumer to crash when reading
+ large message payloads asynchronously.
+
+- Serialization error messages now include the invalid value.
+
+.. _version-0.9.2:
+
+Version 0.9.2
+=============
+
+- Consumer cancel notification support was broken (Issue #1)
+
+ Fix contributed by Andrew Grangaard
+
+.. _version-0.9.1:
+
+Version 0.9.1
+=============
+
+- Supports draining events from multiple channels (``Connection.drain_events``)
+- Support for timeouts
+- Support for heartbeats
+ - ``Connection.heartbeat_tick(rate=2)`` must called at regular intervals
+ (half of the heartbeat value if rate is 2).
+ - Or some other scheme by using ``Connection.send_heartbeat``.
+- Supports RabbitMQ extensions:
+ - Consumer Cancel Notifications
+ - by default a cancel results in ``ChannelError`` being raised
+ - but not if a ``on_cancel`` callback is passed to ``basic_consume``.
+ - Publisher confirms
+ - ``Channel.confirm_select()`` enables publisher confirms.
+ - ``Channel.events['basic_ack'].append(my_callback)`` adds a callback
+ to be called when a message is confirmed. This callback is then
+ called with the signature ``(delivery_tag, multiple)``.
+- Support for ``basic_return``
+- Uses AMQP 0-9-1 instead of 0-8.
+ - ``Channel.access_request`` and ``ticket`` arguments to methods
+ **removed**.
+ - Supports the ``arguments`` argument to ``basic_consume``.
+ - ``internal`` argument to ``exchange_declare`` removed.
+ - ``auto_delete`` argument to ``exchange_declare`` deprecated
+ - ``insist`` argument to ``Connection`` removed.
+ - ``Channel.alerts`` has been removed.
+ - Support for ``Channel.basic_recover_async``.
+ - ``Channel.basic_recover`` deprecated.
+- Exceptions renamed to have idiomatic names:
+ - ``AMQPException`` -> ``AMQPError``
+ - ``AMQPConnectionException`` -> ConnectionError``
+ - ``AMQPChannelException`` -> ChannelError``
+ - ``Connection.known_hosts`` removed.
+ - ``Connection`` no longer supports redirects.
+ - ``exchange`` argument to ``queue_bind`` can now be empty
+ to use the "default exchange".
+- Adds ``Connection.is_alive`` that tries to detect
+ whether the connection can still be used.
+- Adds ``Connection.connection_errors`` and ``.channel_errors``,
+ a list of recoverable errors.
+- Exposes the underlying socket as ``Connection.sock``.
+- Adds ``Channel.no_ack_consumers`` to keep track of consumer tags
+ that set the no_ack flag.
+- Slightly better at error recovery
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..3b473db
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,458 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..d17e9d2
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,6 @@
+include README.rst Changelog LICENSE
+recursive-include docs *
+recursive-include demo *.py
+recursive-include extra README *.py
+recursive-include funtests *.py
+recursive-include requirements *.txt
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..c71dc31
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,128 @@
+Metadata-Version: 1.1
+Name: amqp
+Version: 1.4.5
+Summary: Low-level AMQP client for Python (fork of amqplib)
+Home-page: http://github.com/celery/py-amqp
+Author: Ask Solem
+Author-email: pyamqp@celeryproject.org
+License: LGPL
+Description: =====================================================================
+ Python AMQP 0.9.1 client library
+ =====================================================================
+
+ :Version: 1.4.5
+ :Web: http://amqp.readthedocs.org/
+ :Download: http://pypi.python.org/pypi/amqp/
+ :Source: http://github.com/celery/py-amqp/
+ :Keywords: amqp, rabbitmq
+
+ About
+ =====
+
+ This is a fork of amqplib_ which was originally written by Barry Pederson.
+ It is maintained by the Celery_ project, and used by `kombu`_ as a pure python
+ alternative when `librabbitmq`_ is not available.
+
+ This library should be API compatible with `librabbitmq`_.
+
+ .. _amqplib: http://pypi.python.org/pypi/amqplib
+ .. _Celery: http://celeryproject.org/
+ .. _kombu: http://kombu.readthedocs.org/
+ .. _librabbitmq: http://pypi.python.org/pypi/librabbitmq
+
+ Differences from `amqplib`_
+ ===========================
+
+ - Supports draining events from multiple channels (``Connection.drain_events``)
+ - Support for timeouts
+ - Channels are restored after channel error, instead of having to close the
+ connection.
+ - Support for heartbeats
+
+ - ``Connection.heartbeat_tick(rate=2)`` must called at regular intervals
+ (half of the heartbeat value if rate is 2).
+ - Or some other scheme by using ``Connection.send_heartbeat``.
+ - Supports RabbitMQ extensions:
+ - Consumer Cancel Notifications
+ - by default a cancel results in ``ChannelError`` being raised
+ - but not if a ``on_cancel`` callback is passed to ``basic_consume``.
+ - Publisher confirms
+ - ``Channel.confirm_select()`` enables publisher confirms.
+ - ``Channel.events['basic_ack'].append(my_callback)`` adds a callback
+ to be called when a message is confirmed. This callback is then
+ called with the signature ``(delivery_tag, multiple)``.
+ - Exchange-to-exchange bindings: ``exchange_bind`` / ``exchange_unbind``.
+ - ``Channel.confirm_select()`` enables publisher confirms.
+ - ``Channel.events['basic_ack'].append(my_callback)`` adds a callback
+ to be called when a message is confirmed. This callback is then
+ called with the signature ``(delivery_tag, multiple)``.
+ - Support for ``basic_return``
+ - Uses AMQP 0-9-1 instead of 0-8.
+ - ``Channel.access_request`` and ``ticket`` arguments to methods
+ **removed**.
+ - Supports the ``arguments`` argument to ``basic_consume``.
+ - ``internal`` argument to ``exchange_declare`` removed.
+ - ``auto_delete`` argument to ``exchange_declare`` deprecated
+ - ``insist`` argument to ``Connection`` removed.
+ - ``Channel.alerts`` has been removed.
+ - Support for ``Channel.basic_recover_async``.
+ - ``Channel.basic_recover`` deprecated.
+ - Exceptions renamed to have idiomatic names:
+ - ``AMQPException`` -> ``AMQPError``
+ - ``AMQPConnectionException`` -> ConnectionError``
+ - ``AMQPChannelException`` -> ChannelError``
+ - ``Connection.known_hosts`` removed.
+ - ``Connection`` no longer supports redirects.
+ - ``exchange`` argument to ``queue_bind`` can now be empty
+ to use the "default exchange".
+ - Adds ``Connection.is_alive`` that tries to detect
+ whether the connection can still be used.
+ - Adds ``Connection.connection_errors`` and ``.channel_errors``,
+ a list of recoverable errors.
+ - Exposes the underlying socket as ``Connection.sock``.
+ - Adds ``Channel.no_ack_consumers`` to keep track of consumer tags
+ that set the no_ack flag.
+ - Slightly better at error recovery
+
+ Further
+ =======
+
+ - Differences between AMQP 0.8 and 0.9.1
+
+ http://www.rabbitmq.com/amqp-0-8-to-0-9-1.html
+
+ - AMQP 0.9.1 Quick Reference
+
+ http://www.rabbitmq.com/amqp-0-9-1-quickref.html
+
+ - RabbitMQ Extensions
+
+ http://www.rabbitmq.com/extensions.html
+
+ - For more information about AMQP, visit
+
+ http://www.amqp.org
+
+ - For other Python client libraries see:
+
+ http://www.rabbitmq.com/devtools.html#python-dev
+
+ .. image:: https://d2weczhvl823v0.cloudfront.net/celery/celery/trend.png
+ :alt: Bitdeli badge
+ :target: https://bitdeli.com/free
+
+Platform: any
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.0
+Classifier: Programming Language :: Python :: 3.1
+Classifier: Programming Language :: Python :: 3.2
+Classifier: Programming Language :: Python :: 3.3
+Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: OS Independent
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..661d6bd
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,104 @@
+=====================================================================
+ Python AMQP 0.9.1 client library
+=====================================================================
+
+:Version: 1.4.5
+:Web: http://amqp.readthedocs.org/
+:Download: http://pypi.python.org/pypi/amqp/
+:Source: http://github.com/celery/py-amqp/
+:Keywords: amqp, rabbitmq
+
+About
+=====
+
+This is a fork of amqplib_ which was originally written by Barry Pederson.
+It is maintained by the Celery_ project, and used by `kombu`_ as a pure python
+alternative when `librabbitmq`_ is not available.
+
+This library should be API compatible with `librabbitmq`_.
+
+.. _amqplib: http://pypi.python.org/pypi/amqplib
+.. _Celery: http://celeryproject.org/
+.. _kombu: http://kombu.readthedocs.org/
+.. _librabbitmq: http://pypi.python.org/pypi/librabbitmq
+
+Differences from `amqplib`_
+===========================
+
+- Supports draining events from multiple channels (``Connection.drain_events``)
+- Support for timeouts
+- Channels are restored after channel error, instead of having to close the
+ connection.
+- Support for heartbeats
+
+ - ``Connection.heartbeat_tick(rate=2)`` must called at regular intervals
+ (half of the heartbeat value if rate is 2).
+ - Or some other scheme by using ``Connection.send_heartbeat``.
+- Supports RabbitMQ extensions:
+ - Consumer Cancel Notifications
+ - by default a cancel results in ``ChannelError`` being raised
+ - but not if a ``on_cancel`` callback is passed to ``basic_consume``.
+ - Publisher confirms
+ - ``Channel.confirm_select()`` enables publisher confirms.
+ - ``Channel.events['basic_ack'].append(my_callback)`` adds a callback
+ to be called when a message is confirmed. This callback is then
+ called with the signature ``(delivery_tag, multiple)``.
+ - Exchange-to-exchange bindings: ``exchange_bind`` / ``exchange_unbind``.
+ - ``Channel.confirm_select()`` enables publisher confirms.
+ - ``Channel.events['basic_ack'].append(my_callback)`` adds a callback
+ to be called when a message is confirmed. This callback is then
+ called with the signature ``(delivery_tag, multiple)``.
+- Support for ``basic_return``
+- Uses AMQP 0-9-1 instead of 0-8.
+ - ``Channel.access_request`` and ``ticket`` arguments to methods
+ **removed**.
+ - Supports the ``arguments`` argument to ``basic_consume``.
+ - ``internal`` argument to ``exchange_declare`` removed.
+ - ``auto_delete`` argument to ``exchange_declare`` deprecated
+ - ``insist`` argument to ``Connection`` removed.
+ - ``Channel.alerts`` has been removed.
+ - Support for ``Channel.basic_recover_async``.
+ - ``Channel.basic_recover`` deprecated.
+- Exceptions renamed to have idiomatic names:
+ - ``AMQPException`` -> ``AMQPError``
+ - ``AMQPConnectionException`` -> ConnectionError``
+ - ``AMQPChannelException`` -> ChannelError``
+ - ``Connection.known_hosts`` removed.
+ - ``Connection`` no longer supports redirects.
+ - ``exchange`` argument to ``queue_bind`` can now be empty
+ to use the "default exchange".
+- Adds ``Connection.is_alive`` that tries to detect
+ whether the connection can still be used.
+- Adds ``Connection.connection_errors`` and ``.channel_errors``,
+ a list of recoverable errors.
+- Exposes the underlying socket as ``Connection.sock``.
+- Adds ``Channel.no_ack_consumers`` to keep track of consumer tags
+ that set the no_ack flag.
+- Slightly better at error recovery
+
+Further
+=======
+
+- Differences between AMQP 0.8 and 0.9.1
+
+ http://www.rabbitmq.com/amqp-0-8-to-0-9-1.html
+
+- AMQP 0.9.1 Quick Reference
+
+ http://www.rabbitmq.com/amqp-0-9-1-quickref.html
+
+- RabbitMQ Extensions
+
+ http://www.rabbitmq.com/extensions.html
+
+- For more information about AMQP, visit
+
+ http://www.amqp.org
+
+- For other Python client libraries see:
+
+ http://www.rabbitmq.com/devtools.html#python-dev
+
+.. image:: https://d2weczhvl823v0.cloudfront.net/celery/celery/trend.png
+ :alt: Bitdeli badge
+ :target: https://bitdeli.com/free
diff --git a/amqp.egg-info/PKG-INFO b/amqp.egg-info/PKG-INFO
new file mode 100644
index 0000000..c71dc31
--- /dev/null
+++ b/amqp.egg-info/PKG-INFO
@@ -0,0 +1,128 @@
+Metadata-Version: 1.1
+Name: amqp
+Version: 1.4.5
+Summary: Low-level AMQP client for Python (fork of amqplib)
+Home-page: http://github.com/celery/py-amqp
+Author: Ask Solem
+Author-email: pyamqp@celeryproject.org
+License: LGPL
+Description: =====================================================================
+ Python AMQP 0.9.1 client library
+ =====================================================================
+
+ :Version: 1.4.5
+ :Web: http://amqp.readthedocs.org/
+ :Download: http://pypi.python.org/pypi/amqp/
+ :Source: http://github.com/celery/py-amqp/
+ :Keywords: amqp, rabbitmq
+
+ About
+ =====
+
+ This is a fork of amqplib_ which was originally written by Barry Pederson.
+ It is maintained by the Celery_ project, and used by `kombu`_ as a pure python
+ alternative when `librabbitmq`_ is not available.
+
+ This library should be API compatible with `librabbitmq`_.
+
+ .. _amqplib: http://pypi.python.org/pypi/amqplib
+ .. _Celery: http://celeryproject.org/
+ .. _kombu: http://kombu.readthedocs.org/
+ .. _librabbitmq: http://pypi.python.org/pypi/librabbitmq
+
+ Differences from `amqplib`_
+ ===========================
+
+ - Supports draining events from multiple channels (``Connection.drain_events``)
+ - Support for timeouts
+ - Channels are restored after channel error, instead of having to close the
+ connection.
+ - Support for heartbeats
+
+ - ``Connection.heartbeat_tick(rate=2)`` must called at regular intervals
+ (half of the heartbeat value if rate is 2).
+ - Or some other scheme by using ``Connection.send_heartbeat``.
+ - Supports RabbitMQ extensions:
+ - Consumer Cancel Notifications
+ - by default a cancel results in ``ChannelError`` being raised
+ - but not if a ``on_cancel`` callback is passed to ``basic_consume``.
+ - Publisher confirms
+ - ``Channel.confirm_select()`` enables publisher confirms.
+ - ``Channel.events['basic_ack'].append(my_callback)`` adds a callback
+ to be called when a message is confirmed. This callback is then
+ called with the signature ``(delivery_tag, multiple)``.
+ - Exchange-to-exchange bindings: ``exchange_bind`` / ``exchange_unbind``.
+ - ``Channel.confirm_select()`` enables publisher confirms.
+ - ``Channel.events['basic_ack'].append(my_callback)`` adds a callback
+ to be called when a message is confirmed. This callback is then
+ called with the signature ``(delivery_tag, multiple)``.
+ - Support for ``basic_return``
+ - Uses AMQP 0-9-1 instead of 0-8.
+ - ``Channel.access_request`` and ``ticket`` arguments to methods
+ **removed**.
+ - Supports the ``arguments`` argument to ``basic_consume``.
+ - ``internal`` argument to ``exchange_declare`` removed.
+ - ``auto_delete`` argument to ``exchange_declare`` deprecated
+ - ``insist`` argument to ``Connection`` removed.
+ - ``Channel.alerts`` has been removed.
+ - Support for ``Channel.basic_recover_async``.
+ - ``Channel.basic_recover`` deprecated.
+ - Exceptions renamed to have idiomatic names:
+ - ``AMQPException`` -> ``AMQPError``
+ - ``AMQPConnectionException`` -> ConnectionError``
+ - ``AMQPChannelException`` -> ChannelError``
+ - ``Connection.known_hosts`` removed.
+ - ``Connection`` no longer supports redirects.
+ - ``exchange`` argument to ``queue_bind`` can now be empty
+ to use the "default exchange".
+ - Adds ``Connection.is_alive`` that tries to detect
+ whether the connection can still be used.
+ - Adds ``Connection.connection_errors`` and ``.channel_errors``,
+ a list of recoverable errors.
+ - Exposes the underlying socket as ``Connection.sock``.
+ - Adds ``Channel.no_ack_consumers`` to keep track of consumer tags
+ that set the no_ack flag.
+ - Slightly better at error recovery
+
+ Further
+ =======
+
+ - Differences between AMQP 0.8 and 0.9.1
+
+ http://www.rabbitmq.com/amqp-0-8-to-0-9-1.html
+
+ - AMQP 0.9.1 Quick Reference
+
+ http://www.rabbitmq.com/amqp-0-9-1-quickref.html
+
+ - RabbitMQ Extensions
+
+ http://www.rabbitmq.com/extensions.html
+
+ - For more information about AMQP, visit
+
+ http://www.amqp.org
+
+ - For other Python client libraries see:
+
+ http://www.rabbitmq.com/devtools.html#python-dev
+
+ .. image:: https://d2weczhvl823v0.cloudfront.net/celery/celery/trend.png
+ :alt: Bitdeli badge
+ :target: https://bitdeli.com/free
+
+Platform: any
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.0
+Classifier: Programming Language :: Python :: 3.1
+Classifier: Programming Language :: Python :: 3.2
+Classifier: Programming Language :: Python :: 3.3
+Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: OS Independent
diff --git a/amqp.egg-info/SOURCES.txt b/amqp.egg-info/SOURCES.txt
new file mode 100644
index 0000000..dd32cc2
--- /dev/null
+++ b/amqp.egg-info/SOURCES.txt
@@ -0,0 +1,67 @@
+Changelog
+LICENSE
+MANIFEST.in
+README.rst
+setup.py
+amqp/__init__.py
+amqp/abstract_channel.py
+amqp/basic_message.py
+amqp/channel.py
+amqp/connection.py
+amqp/exceptions.py
+amqp/five.py
+amqp/method_framing.py
+amqp/protocol.py
+amqp/serialization.py
+amqp/transport.py
+amqp/utils.py
+amqp.egg-info/PKG-INFO
+amqp.egg-info/SOURCES.txt
+amqp.egg-info/dependency_links.txt
+amqp.egg-info/not-zip-safe
+amqp.egg-info/top_level.txt
+demo/amqp_clock.py
+demo/demo_receive.py
+demo/demo_send.py
+docs/Makefile
+docs/changelog.rst
+docs/conf.py
+docs/index.rst
+docs/.static/.keep
+docs/.templates/page.html
+docs/.templates/sidebarintro.html
+docs/.templates/sidebarlogo.html
+docs/_ext/applyxrefs.py
+docs/_ext/literals_to_xrefs.py
+docs/_theme/celery/theme.conf
+docs/_theme/celery/static/celery.css_t
+docs/includes/intro.txt
+docs/reference/amqp.abstract_channel.rst
+docs/reference/amqp.basic_message.rst
+docs/reference/amqp.channel.rst
+docs/reference/amqp.connection.rst
+docs/reference/amqp.exceptions.rst
+docs/reference/amqp.five.rst
+docs/reference/amqp.method_framing.rst
+docs/reference/amqp.protocol.rst
+docs/reference/amqp.serialization.rst
+docs/reference/amqp.transport.rst
+docs/reference/amqp.utils.rst
+docs/reference/index.rst
+docs/templates/readme.txt
+extra/README
+extra/generate_skeleton_0_8.py
+extra/update_comments_from_spec.py
+extra/release/bump_version.py
+extra/release/sphinx-to-rst.py
+funtests/run_all.py
+funtests/settings.py
+funtests/test_basic_message.py
+funtests/test_channel.py
+funtests/test_connection.py
+funtests/test_exceptions.py
+funtests/test_serialization.py
+funtests/test_with.py
+requirements/docs.txt
+requirements/pkgutils.txt
+requirements/test.txt \ No newline at end of file
diff --git a/amqp.egg-info/dependency_links.txt b/amqp.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/amqp.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/amqp.egg-info/not-zip-safe b/amqp.egg-info/not-zip-safe
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/amqp.egg-info/not-zip-safe
@@ -0,0 +1 @@
+
diff --git a/amqp.egg-info/top_level.txt b/amqp.egg-info/top_level.txt
new file mode 100644
index 0000000..5e610d3
--- /dev/null
+++ b/amqp.egg-info/top_level.txt
@@ -0,0 +1 @@
+amqp
diff --git a/amqp/__init__.py b/amqp/__init__.py
new file mode 100644
index 0000000..3bd57c9
--- /dev/null
+++ b/amqp/__init__.py
@@ -0,0 +1,70 @@
+"""Low-level AMQP client for Python (fork of amqplib)"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+from __future__ import absolute_import
+
+VERSION = (1, 4, 5)
+__version__ = '.'.join(map(str, VERSION[0:3])) + ''.join(VERSION[3:])
+__author__ = 'Barry Pederson'
+__maintainer__ = 'Ask Solem'
+__contact__ = 'pyamqp@celeryproject.org'
+__homepage__ = 'http://github.com/celery/py-amqp'
+__docformat__ = 'restructuredtext'
+
+# -eof meta-
+
+#
+# Pull in the public items from the various sub-modules
+#
+from .basic_message import Message # noqa
+from .channel import Channel # noqa
+from .connection import Connection # noqa
+from .exceptions import ( # noqa
+ AMQPError,
+ ConnectionError,
+ RecoverableConnectionError,
+ IrrecoverableConnectionError,
+ ChannelError,
+ RecoverableChannelError,
+ IrrecoverableChannelError,
+ ConsumerCancelled,
+ ContentTooLarge,
+ NoConsumers,
+ ConnectionForced,
+ InvalidPath,
+ AccessRefused,
+ NotFound,
+ ResourceLocked,
+ PreconditionFailed,
+ FrameError,
+ FrameSyntaxError,
+ InvalidCommand,
+ ChannelNotOpen,
+ UnexpectedFrame,
+ ResourceError,
+ NotAllowed,
+ AMQPNotImplementedError,
+ InternalError,
+ error_for_code,
+ __all__ as _all_exceptions,
+)
+from .utils import promise # noqa
+
+__all__ = [
+ 'Connection',
+ 'Channel',
+ 'Message',
+] + _all_exceptions
diff --git a/amqp/abstract_channel.py b/amqp/abstract_channel.py
new file mode 100644
index 0000000..28cfe13
--- /dev/null
+++ b/amqp/abstract_channel.py
@@ -0,0 +1,93 @@
+"""Code common to Connection and Channel objects."""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>)
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+from __future__ import absolute_import
+
+from .exceptions import AMQPNotImplementedError, RecoverableConnectionError
+from .serialization import AMQPWriter
+
+__all__ = ['AbstractChannel']
+
+
+class AbstractChannel(object):
+ """Superclass for both the Connection, which is treated
+ as channel 0, and other user-created Channel objects.
+
+ The subclasses must have a _METHOD_MAP class property, mapping
+ between AMQP method signatures and Python methods.
+
+ """
+ def __init__(self, connection, channel_id):
+ self.connection = connection
+ self.channel_id = channel_id
+ connection.channels[channel_id] = self
+ self.method_queue = [] # Higher level queue for methods
+ self.auto_decode = False
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *exc_info):
+ self.close()
+
+ def _send_method(self, method_sig, args=bytes(), content=None):
+ """Send a method for our channel."""
+ conn = self.connection
+ if conn is None:
+ raise RecoverableConnectionError('connection already closed')
+
+ if isinstance(args, AMQPWriter):
+ args = args.getvalue()
+
+ conn.method_writer.write_method(
+ self.channel_id, method_sig, args, content,
+ )
+
+ def close(self):
+ """Close this Channel or Connection"""
+ raise NotImplementedError('Must be overriden in subclass')
+
+ def wait(self, allowed_methods=None):
+ """Wait for a method that matches our allowed_methods parameter (the
+ default value of None means match any method), and dispatch to it."""
+ method_sig, args, content = self.connection._wait_method(
+ self.channel_id, allowed_methods)
+
+ return self.dispatch_method(method_sig, args, content)
+
+ def dispatch_method(self, method_sig, args, content):
+ if content and \
+ self.auto_decode and \
+ hasattr(content, 'content_encoding'):
+ try:
+ content.body = content.body.decode(content.content_encoding)
+ except Exception:
+ pass
+
+ try:
+ amqp_method = self._METHOD_MAP[method_sig]
+ except KeyError:
+ raise AMQPNotImplementedError(
+ 'Unknown AMQP method {0!r}'.format(method_sig))
+
+ if content is None:
+ return amqp_method(self, args)
+ else:
+ return amqp_method(self, args, content)
+
+ #: Placeholder, the concrete implementations will have to
+ #: supply their own versions of _METHOD_MAP
+ _METHOD_MAP = {}
diff --git a/amqp/basic_message.py b/amqp/basic_message.py
new file mode 100644
index 0000000..192ede9
--- /dev/null
+++ b/amqp/basic_message.py
@@ -0,0 +1,124 @@
+"""Messages for AMQP"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+from __future__ import absolute_import
+
+from .serialization import GenericContent
+
+__all__ = ['Message']
+
+
+class Message(GenericContent):
+ """A Message for use with the Channnel.basic_* methods."""
+
+ #: Instances of this class have these attributes, which
+ #: are passed back and forth as message properties between
+ #: client and server
+ PROPERTIES = [
+ ('content_type', 'shortstr'),
+ ('content_encoding', 'shortstr'),
+ ('application_headers', 'table'),
+ ('delivery_mode', 'octet'),
+ ('priority', 'octet'),
+ ('correlation_id', 'shortstr'),
+ ('reply_to', 'shortstr'),
+ ('expiration', 'shortstr'),
+ ('message_id', 'shortstr'),
+ ('timestamp', 'timestamp'),
+ ('type', 'shortstr'),
+ ('user_id', 'shortstr'),
+ ('app_id', 'shortstr'),
+ ('cluster_id', 'shortstr')
+ ]
+
+ def __init__(self, body='', children=None, channel=None, **properties):
+ """Expected arg types
+
+ body: string
+ children: (not supported)
+
+ Keyword properties may include:
+
+ content_type: shortstr
+ MIME content type
+
+ content_encoding: shortstr
+ MIME content encoding
+
+ application_headers: table
+ Message header field table, a dict with string keys,
+ and string | int | Decimal | datetime | dict values.
+
+ delivery_mode: octet
+ Non-persistent (1) or persistent (2)
+
+ priority: octet
+ The message priority, 0 to 9
+
+ correlation_id: shortstr
+ The application correlation identifier
+
+ reply_to: shortstr
+ The destination to reply to
+
+ expiration: shortstr
+ Message expiration specification
+
+ message_id: shortstr
+ The application message identifier
+
+ timestamp: datetime.datetime
+ The message timestamp
+
+ type: shortstr
+ The message type name
+
+ user_id: shortstr
+ The creating user id
+
+ app_id: shortstr
+ The creating application id
+
+ cluster_id: shortstr
+ Intra-cluster routing identifier
+
+ Unicode bodies are encoded according to the 'content_encoding'
+ argument. If that's None, it's set to 'UTF-8' automatically.
+
+ example::
+
+ msg = Message('hello world',
+ content_type='text/plain',
+ application_headers={'foo': 7})
+
+ """
+ super(Message, self).__init__(**properties)
+ self.body = body
+ self.channel = channel
+
+ def __eq__(self, other):
+ """Check if the properties and bodies of this Message and another
+ Message are the same.
+
+ Received messages may contain a 'delivery_info' attribute,
+ which isn't compared.
+
+ """
+ try:
+ return (super(Message, self).__eq__(other) and
+ self.body == other.body)
+ except AttributeError:
+ return NotImplemented
diff --git a/amqp/channel.py b/amqp/channel.py
new file mode 100644
index 0000000..05eb09a
--- /dev/null
+++ b/amqp/channel.py
@@ -0,0 +1,2537 @@
+"""AMQP Channels"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+from __future__ import absolute_import
+
+import logging
+
+from collections import defaultdict
+from warnings import warn
+
+from .abstract_channel import AbstractChannel
+from .exceptions import ChannelError, ConsumerCancelled, error_for_code
+from .five import Queue
+from .protocol import basic_return_t, queue_declare_ok_t
+from .serialization import AMQPWriter
+
+__all__ = ['Channel']
+
+AMQP_LOGGER = logging.getLogger('amqp')
+
+EXCHANGE_AUTODELETE_DEPRECATED = """\
+The auto_delete flag for exchanges has been deprecated and will be removed
+from py-amqp v1.5.0.\
+"""
+
+
+class VDeprecationWarning(DeprecationWarning):
+ pass
+
+
+class Channel(AbstractChannel):
+ """Work with channels
+
+ The channel class provides methods for a client to establish a
+ virtual connection - a channel - to a server and for both peers to
+ operate the virtual connection thereafter.
+
+ GRAMMAR::
+
+ channel = open-channel *use-channel close-channel
+ open-channel = C:OPEN S:OPEN-OK
+ use-channel = C:FLOW S:FLOW-OK
+ / S:FLOW C:FLOW-OK
+ / functional-class
+ close-channel = C:CLOSE S:CLOSE-OK
+ / S:CLOSE C:CLOSE-OK
+
+ """
+
+ def __init__(self, connection, channel_id=None, auto_decode=True):
+ """Create a channel bound to a connection and using the specified
+ numeric channel_id, and open on the server.
+
+ The 'auto_decode' parameter (defaults to True), indicates
+ whether the library should attempt to decode the body
+ of Messages to a Unicode string if there's a 'content_encoding'
+ property for the message. If there's no 'content_encoding'
+ property, or the decode raises an Exception, the message body
+ is left as plain bytes.
+
+ """
+ if channel_id:
+ connection._claim_channel_id(channel_id)
+ else:
+ channel_id = connection._get_free_channel_id()
+
+ AMQP_LOGGER.debug('using channel_id: %d', channel_id)
+
+ super(Channel, self).__init__(connection, channel_id)
+
+ self.is_open = False
+ self.active = True # Flow control
+ self.returned_messages = Queue()
+ self.callbacks = {}
+ self.cancel_callbacks = {}
+ self.auto_decode = auto_decode
+ self.events = defaultdict(set)
+ self.no_ack_consumers = set()
+
+ # set first time basic_publish_confirm is called
+ # and publisher confirms are enabled for this channel.
+ self._confirm_selected = False
+ if self.connection.confirm_publish:
+ self.basic_publish = self.basic_publish_confirm
+
+ self._x_open()
+
+ def _do_close(self):
+ """Tear down this object, after we've agreed to close
+ with the server."""
+ AMQP_LOGGER.debug('Closed channel #%d', self.channel_id)
+ self.is_open = False
+ channel_id, self.channel_id = self.channel_id, None
+ connection, self.connection = self.connection, None
+ if connection:
+ connection.channels.pop(channel_id, None)
+ connection._avail_channel_ids.append(channel_id)
+ self.callbacks.clear()
+ self.cancel_callbacks.clear()
+ self.events.clear()
+ self.no_ack_consumers.clear()
+
+ def _do_revive(self):
+ self.is_open = False
+ self._x_open()
+
+ def close(self, reply_code=0, reply_text='', method_sig=(0, 0)):
+ """Request a channel close
+
+ This method indicates that the sender wants to close the
+ channel. This may be due to internal conditions (e.g. a forced
+ shut-down) or due to an error handling a specific method, i.e.
+ an exception. When a close is due to an exception, the sender
+ provides the class and method id of the method which caused
+ the exception.
+
+ RULE:
+
+ After sending this method any received method except
+ Channel.Close-OK MUST be discarded.
+
+ RULE:
+
+ The peer sending this method MAY use a counter or timeout
+ to detect failure of the other peer to respond correctly
+ with Channel.Close-OK..
+
+ PARAMETERS:
+ reply_code: short
+
+ The reply code. The AMQ reply codes are defined in AMQ
+ RFC 011.
+
+ reply_text: shortstr
+
+ The localised reply text. This text can be logged as an
+ aid to resolving issues.
+
+ class_id: short
+
+ failing method class
+
+ When the close is provoked by a method exception, this
+ is the class of the method.
+
+ method_id: short
+
+ failing method ID
+
+ When the close is provoked by a method exception, this
+ is the ID of the method.
+
+ """
+ try:
+ if not self.is_open or self.connection is None:
+ return
+
+ args = AMQPWriter()
+ args.write_short(reply_code)
+ args.write_shortstr(reply_text)
+ args.write_short(method_sig[0]) # class_id
+ args.write_short(method_sig[1]) # method_id
+ self._send_method((20, 40), args)
+ return self.wait(allowed_methods=[
+ (20, 40), # Channel.close
+ (20, 41), # Channel.close_ok
+ ])
+ finally:
+ self.connection = None
+
+ def _close(self, args):
+ """Request a channel close
+
+ This method indicates that the sender wants to close the
+ channel. This may be due to internal conditions (e.g. a forced
+ shut-down) or due to an error handling a specific method, i.e.
+ an exception. When a close is due to an exception, the sender
+ provides the class and method id of the method which caused
+ the exception.
+
+ RULE:
+
+ After sending this method any received method except
+ Channel.Close-OK MUST be discarded.
+
+ RULE:
+
+ The peer sending this method MAY use a counter or timeout
+ to detect failure of the other peer to respond correctly
+ with Channel.Close-OK..
+
+ PARAMETERS:
+ reply_code: short
+
+ The reply code. The AMQ reply codes are defined in AMQ
+ RFC 011.
+
+ reply_text: shortstr
+
+ The localised reply text. This text can be logged as an
+ aid to resolving issues.
+
+ class_id: short
+
+ failing method class
+
+ When the close is provoked by a method exception, this
+ is the class of the method.
+
+ method_id: short
+
+ failing method ID
+
+ When the close is provoked by a method exception, this
+ is the ID of the method.
+
+ """
+
+ reply_code = args.read_short()
+ reply_text = args.read_shortstr()
+ class_id = args.read_short()
+ method_id = args.read_short()
+
+ self._send_method((20, 41))
+ self._do_revive()
+
+ raise error_for_code(
+ reply_code, reply_text, (class_id, method_id), ChannelError,
+ )
+
+ def _close_ok(self, args):
+ """Confirm a channel close
+
+ This method confirms a Channel.Close method and tells the
+ recipient that it is safe to release resources for the channel
+ and close the socket.
+
+ RULE:
+
+ A peer that detects a socket closure without having
+ received a Channel.Close-Ok handshake method SHOULD log
+ the error.
+
+ """
+ self._do_close()
+
+ def flow(self, active):
+ """Enable/disable flow from peer
+
+ This method asks the peer to pause or restart the flow of
+ content data. This is a simple flow-control mechanism that a
+ peer can use to avoid oveflowing its queues or otherwise
+ finding itself receiving more messages than it can process.
+ Note that this method is not intended for window control. The
+ peer that receives a request to stop sending content should
+ finish sending the current content, if any, and then wait
+ until it receives a Flow restart method.
+
+ RULE:
+
+ When a new channel is opened, it is active. Some
+ applications assume that channels are inactive until
+ started. To emulate this behaviour a client MAY open the
+ channel, then pause it.
+
+ RULE:
+
+ When sending content data in multiple frames, a peer
+ SHOULD monitor the channel for incoming methods and
+ respond to a Channel.Flow as rapidly as possible.
+
+ RULE:
+
+ A peer MAY use the Channel.Flow method to throttle
+ incoming content data for internal reasons, for example,
+ when exchangeing data over a slower connection.
+
+ RULE:
+
+ The peer that requests a Channel.Flow method MAY
+ disconnect and/or ban a peer that does not respect the
+ request.
+
+ PARAMETERS:
+ active: boolean
+
+ start/stop content frames
+
+ If True, the peer starts sending content frames. If
+ False, the peer stops sending content frames.
+
+ """
+ args = AMQPWriter()
+ args.write_bit(active)
+ self._send_method((20, 20), args)
+ return self.wait(allowed_methods=[
+ (20, 21), # Channel.flow_ok
+ ])
+
+ def _flow(self, args):
+ """Enable/disable flow from peer
+
+ This method asks the peer to pause or restart the flow of
+ content data. This is a simple flow-control mechanism that a
+ peer can use to avoid oveflowing its queues or otherwise
+ finding itself receiving more messages than it can process.
+ Note that this method is not intended for window control. The
+ peer that receives a request to stop sending content should
+ finish sending the current content, if any, and then wait
+ until it receives a Flow restart method.
+
+ RULE:
+
+ When a new channel is opened, it is active. Some
+ applications assume that channels are inactive until
+ started. To emulate this behaviour a client MAY open the
+ channel, then pause it.
+
+ RULE:
+
+ When sending content data in multiple frames, a peer
+ SHOULD monitor the channel for incoming methods and
+ respond to a Channel.Flow as rapidly as possible.
+
+ RULE:
+
+ A peer MAY use the Channel.Flow method to throttle
+ incoming content data for internal reasons, for example,
+ when exchangeing data over a slower connection.
+
+ RULE:
+
+ The peer that requests a Channel.Flow method MAY
+ disconnect and/or ban a peer that does not respect the
+ request.
+
+ PARAMETERS:
+ active: boolean
+
+ start/stop content frames
+
+ If True, the peer starts sending content frames. If
+ False, the peer stops sending content frames.
+
+ """
+ self.active = args.read_bit()
+ self._x_flow_ok(self.active)
+
+ def _x_flow_ok(self, active):
+ """Confirm a flow method
+
+ Confirms to the peer that a flow command was received and
+ processed.
+
+ PARAMETERS:
+ active: boolean
+
+ current flow setting
+
+ Confirms the setting of the processed flow method:
+ True means the peer will start sending or continue
+ to send content frames; False means it will not.
+
+ """
+ args = AMQPWriter()
+ args.write_bit(active)
+ self._send_method((20, 21), args)
+
+ def _flow_ok(self, args):
+ """Confirm a flow method
+
+ Confirms to the peer that a flow command was received and
+ processed.
+
+ PARAMETERS:
+ active: boolean
+
+ current flow setting
+
+ Confirms the setting of the processed flow method:
+ True means the peer will start sending or continue
+ to send content frames; False means it will not.
+
+ """
+ return args.read_bit()
+
+ def _x_open(self):
+ """Open a channel for use
+
+ This method opens a virtual connection (a channel).
+
+ RULE:
+
+ This method MUST NOT be called when the channel is already
+ open.
+
+ PARAMETERS:
+ out_of_band: shortstr (DEPRECATED)
+
+ out-of-band settings
+
+ Configures out-of-band transfers on this channel. The
+ syntax and meaning of this field will be formally
+ defined at a later date.
+
+ """
+ if self.is_open:
+ return
+
+ args = AMQPWriter()
+ args.write_shortstr('') # out_of_band: deprecated
+ self._send_method((20, 10), args)
+ return self.wait(allowed_methods=[
+ (20, 11), # Channel.open_ok
+ ])
+
+ def _open_ok(self, args):
+ """Signal that the channel is ready
+
+ This method signals to the client that the channel is ready
+ for use.
+
+ """
+ self.is_open = True
+ AMQP_LOGGER.debug('Channel open')
+
+ #############
+ #
+ # Exchange
+ #
+ #
+ # work with exchanges
+ #
+ # Exchanges match and distribute messages across queues.
+ # Exchanges can be configured in the server or created at runtime.
+ #
+ # GRAMMAR::
+ #
+ # exchange = C:DECLARE S:DECLARE-OK
+ # / C:DELETE S:DELETE-OK
+ #
+ # RULE:
+ #
+ # The server MUST implement the direct and fanout exchange
+ # types, and predeclare the corresponding exchanges named
+ # amq.direct and amq.fanout in each virtual host. The server
+ # MUST also predeclare a direct exchange to act as the default
+ # exchange for content Publish methods and for default queue
+ # bindings.
+ #
+ # RULE:
+ #
+ # The server SHOULD implement the topic exchange type, and
+ # predeclare the corresponding exchange named amq.topic in
+ # each virtual host.
+ #
+ # RULE:
+ #
+ # The server MAY implement the system exchange type, and
+ # predeclare the corresponding exchanges named amq.system in
+ # each virtual host. If the client attempts to bind a queue to
+ # the system exchange, the server MUST raise a connection
+ # exception with reply code 507 (not allowed).
+ #
+
+ def exchange_declare(self, exchange, type, passive=False, durable=False,
+ auto_delete=True, nowait=False, arguments=None):
+ """Declare exchange, create if needed
+
+ This method creates an exchange if it does not already exist,
+ and if the exchange exists, verifies that it is of the correct
+ and expected class.
+
+ RULE:
+
+ The server SHOULD support a minimum of 16 exchanges per
+ virtual host and ideally, impose no limit except as
+ defined by available resources.
+
+ PARAMETERS:
+ exchange: shortstr
+
+ RULE:
+
+ Exchange names starting with "amq." are reserved
+ for predeclared and standardised exchanges. If
+ the client attempts to create an exchange starting
+ with "amq.", the server MUST raise a channel
+ exception with reply code 403 (access refused).
+
+ type: shortstr
+
+ exchange type
+
+ Each exchange belongs to one of a set of exchange
+ types implemented by the server. The exchange types
+ define the functionality of the exchange - i.e. how
+ messages are routed through it. It is not valid or
+ meaningful to attempt to change the type of an
+ existing exchange.
+
+ RULE:
+
+ If the exchange already exists with a different
+ type, the server MUST raise a connection exception
+ with a reply code 507 (not allowed).
+
+ RULE:
+
+ If the server does not support the requested
+ exchange type it MUST raise a connection exception
+ with a reply code 503 (command invalid).
+
+ passive: boolean
+
+ do not create exchange
+
+ If set, the server will not create the exchange. The
+ client can use this to check whether an exchange
+ exists without modifying the server state.
+
+ RULE:
+
+ If set, and the exchange does not already exist,
+ the server MUST raise a channel exception with
+ reply code 404 (not found).
+
+ durable: boolean
+
+ request a durable exchange
+
+ If set when creating a new exchange, the exchange will
+ be marked as durable. Durable exchanges remain active
+ when a server restarts. Non-durable exchanges
+ (transient exchanges) are purged if/when a server
+ restarts.
+
+ RULE:
+
+ The server MUST support both durable and transient
+ exchanges.
+
+ RULE:
+
+ The server MUST ignore the durable field if the
+ exchange already exists.
+
+ auto_delete: boolean
+
+ auto-delete when unused
+
+ If set, the exchange is deleted when all queues have
+ finished using it.
+
+ RULE:
+
+ The server SHOULD allow for a reasonable delay
+ between the point when it determines that an
+ exchange is not being used (or no longer used),
+ and the point when it deletes the exchange. At
+ the least it must allow a client to create an
+ exchange and then bind a queue to it, with a small
+ but non-zero delay between these two actions.
+
+ RULE:
+
+ The server MUST ignore the auto-delete field if
+ the exchange already exists.
+
+ nowait: boolean
+
+ do not send a reply method
+
+ If set, the server will not respond to the method. The
+ client should not wait for a reply method. If the
+ server could not complete the method it will raise a
+ channel or connection exception.
+
+ arguments: table
+
+ arguments for declaration
+
+ A set of arguments for the declaration. The syntax and
+ semantics of these arguments depends on the server
+ implementation. This field is ignored if passive is
+ True.
+
+ """
+ arguments = {} if arguments is None else arguments
+ args = AMQPWriter()
+ args.write_short(0)
+ args.write_shortstr(exchange)
+ args.write_shortstr(type)
+ args.write_bit(passive)
+ args.write_bit(durable)
+ args.write_bit(auto_delete)
+ args.write_bit(False) # internal: deprecated
+ args.write_bit(nowait)
+ args.write_table(arguments)
+ self._send_method((40, 10), args)
+
+ if auto_delete:
+ warn(VDeprecationWarning(EXCHANGE_AUTODELETE_DEPRECATED))
+
+ if not nowait:
+ return self.wait(allowed_methods=[
+ (40, 11), # Channel.exchange_declare_ok
+ ])
+
+ def _exchange_declare_ok(self, args):
+ """Confirms an exchange declaration
+
+ This method confirms a Declare method and confirms the name of
+ the exchange, essential for automatically-named exchanges.
+
+ """
+ pass
+
+ def exchange_delete(self, exchange, if_unused=False, nowait=False):
+ """Delete an exchange
+
+ This method deletes an exchange. When an exchange is deleted
+ all queue bindings on the exchange are cancelled.
+
+ PARAMETERS:
+ exchange: shortstr
+
+ RULE:
+
+ The exchange MUST exist. Attempting to delete a
+ non-existing exchange causes a channel exception.
+
+ if_unused: boolean
+
+ delete only if unused
+
+ If set, the server will only delete the exchange if it
+ has no queue bindings. If the exchange has queue
+ bindings the server does not delete it but raises a
+ channel exception instead.
+
+ RULE:
+
+ If set, the server SHOULD delete the exchange but
+ only if it has no queue bindings.
+
+ RULE:
+
+ If set, the server SHOULD raise a channel
+ exception if the exchange is in use.
+
+ nowait: boolean
+
+ do not send a reply method
+
+ If set, the server will not respond to the method. The
+ client should not wait for a reply method. If the
+ server could not complete the method it will raise a
+ channel or connection exception.
+
+ """
+ args = AMQPWriter()
+ args.write_short(0)
+ args.write_shortstr(exchange)
+ args.write_bit(if_unused)
+ args.write_bit(nowait)
+ self._send_method((40, 20), args)
+
+ if not nowait:
+ return self.wait(allowed_methods=[
+ (40, 21), # Channel.exchange_delete_ok
+ ])
+
+ def _exchange_delete_ok(self, args):
+ """Confirm deletion of an exchange
+
+ This method confirms the deletion of an exchange.
+
+ """
+ pass
+
+ def exchange_bind(self, destination, source='', routing_key='',
+ nowait=False, arguments=None):
+ """This method binds an exchange to an exchange.
+
+ RULE:
+
+ A server MUST allow and ignore duplicate bindings - that
+ is, two or more bind methods for a specific exchanges,
+ with identical arguments - without treating these as an
+ error.
+
+ RULE:
+
+ A server MUST allow cycles of exchange bindings to be
+ created including allowing an exchange to be bound to
+ itself.
+
+ RULE:
+
+ A server MUST not deliver the same message more than once
+ to a destination exchange, even if the topology of
+ exchanges and bindings results in multiple (even infinite)
+ routes to that exchange.
+
+ PARAMETERS:
+ reserved-1: short
+
+ destination: shortstr
+
+ Specifies the name of the destination exchange to
+ bind.
+
+ RULE:
+
+ A client MUST NOT be allowed to bind a non-
+ existent destination exchange.
+
+ RULE:
+
+ The server MUST accept a blank exchange name to
+ mean the default exchange.
+
+ source: shortstr
+
+ Specifies the name of the source exchange to bind.
+
+ RULE:
+
+ A client MUST NOT be allowed to bind a non-
+ existent source exchange.
+
+ RULE:
+
+ The server MUST accept a blank exchange name to
+ mean the default exchange.
+
+ routing-key: shortstr
+
+ Specifies the routing key for the binding. The routing
+ key is used for routing messages depending on the
+ exchange configuration. Not all exchanges use a
+ routing key - refer to the specific exchange
+ documentation.
+
+ no-wait: bit
+
+ arguments: table
+
+ A set of arguments for the binding. The syntax and
+ semantics of these arguments depends on the exchange
+ class.
+
+ """
+ arguments = {} if arguments is None else arguments
+ args = AMQPWriter()
+ args.write_short(0)
+ args.write_shortstr(destination)
+ args.write_shortstr(source)
+ args.write_shortstr(routing_key)
+ args.write_bit(nowait)
+ args.write_table(arguments)
+ self._send_method((40, 30), args)
+
+ if not nowait:
+ return self.wait(allowed_methods=[
+ (40, 31), # Channel.exchange_bind_ok
+ ])
+
+ def exchange_unbind(self, destination, source='', routing_key='',
+ nowait=False, arguments=None):
+ """This method unbinds an exchange from an exchange.
+
+ RULE:
+
+ If a unbind fails, the server MUST raise a connection
+ exception.
+
+ PARAMETERS:
+ reserved-1: short
+
+ destination: shortstr
+
+ Specifies the name of the destination exchange to
+ unbind.
+
+ RULE:
+
+ The client MUST NOT attempt to unbind an exchange
+ that does not exist from an exchange.
+
+ RULE:
+
+ The server MUST accept a blank exchange name to
+ mean the default exchange.
+
+ source: shortstr
+
+ Specifies the name of the source exchange to unbind.
+
+ RULE:
+
+ The client MUST NOT attempt to unbind an exchange
+ from an exchange that does not exist.
+
+ RULE:
+
+ The server MUST accept a blank exchange name to
+ mean the default exchange.
+
+ routing-key: shortstr
+
+ Specifies the routing key of the binding to unbind.
+
+ no-wait: bit
+
+ arguments: table
+
+ Specifies the arguments of the binding to unbind.
+
+ """
+ arguments = {} if arguments is None else arguments
+ args = AMQPWriter()
+ args.write_short(0)
+ args.write_shortstr(destination)
+ args.write_shortstr(source)
+ args.write_shortstr(routing_key)
+ args.write_bit(nowait)
+ args.write_table(arguments)
+ self._send_method((40, 40), args)
+
+ if not nowait:
+ return self.wait(allowed_methods=[
+ (40, 51), # Channel.exchange_unbind_ok
+ ])
+
+ def _exchange_bind_ok(self, args):
+ """Confirm bind successful
+
+ This method confirms that the bind was successful.
+
+ """
+ pass
+
+ def _exchange_unbind_ok(self, args):
+ """Confirm unbind successful
+
+ This method confirms that the unbind was successful.
+
+ """
+ pass
+
+ #############
+ #
+ # Queue
+ #
+ #
+ # work with queues
+ #
+ # Queues store and forward messages. Queues can be configured in
+ # the server or created at runtime. Queues must be attached to at
+ # least one exchange in order to receive messages from publishers.
+ #
+ # GRAMMAR::
+ #
+ # queue = C:DECLARE S:DECLARE-OK
+ # / C:BIND S:BIND-OK
+ # / C:PURGE S:PURGE-OK
+ # / C:DELETE S:DELETE-OK
+ #
+ # RULE:
+ #
+ # A server MUST allow any content class to be sent to any
+ # queue, in any mix, and queue and delivery these content
+ # classes independently. Note that all methods that fetch
+ # content off queues are specific to a given content class.
+ #
+
+ def queue_bind(self, queue, exchange='', routing_key='',
+ nowait=False, arguments=None):
+ """Bind queue to an exchange
+
+ 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 dest
+ exchange and subscription queues are bound to a dest_wild
+ exchange.
+
+ RULE:
+
+ A server MUST allow ignore duplicate bindings - that is,
+ two or more bind methods for a specific queue, with
+ identical arguments - without treating these as an error.
+
+ RULE:
+
+ If a bind fails, the server MUST raise a connection
+ exception.
+
+ RULE:
+
+ The server MUST NOT allow a durable queue to bind to a
+ transient exchange. If the client attempts this the server
+ MUST raise a channel exception.
+
+ RULE:
+
+ Bindings for durable queues are automatically durable and
+ the server SHOULD restore such bindings after a server
+ restart.
+
+ RULE:
+
+ The server SHOULD support at least 4 bindings per queue,
+ and ideally, impose no limit except as defined by
+ available resources.
+
+ PARAMETERS:
+ queue: shortstr
+
+ Specifies the name of the queue to bind. If the queue
+ name is empty, refers to the current queue for the
+ channel, which is the last declared queue.
+
+ RULE:
+
+ If the client did not previously declare a queue,
+ and the queue name in this method is empty, the
+ server MUST raise a connection exception with
+ reply code 530 (not allowed).
+
+ RULE:
+
+ If the queue does not exist the server MUST raise
+ a channel exception with reply code 404 (not
+ found).
+
+ exchange: shortstr
+
+ The name of the exchange to bind to.
+
+ RULE:
+
+ If the exchange does not exist the server MUST
+ raise a channel exception with reply code 404 (not
+ found).
+
+ routing_key: shortstr
+
+ message routing key
+
+ Specifies the routing key for the binding. The
+ routing key is used for routing messages depending on
+ the exchange configuration. Not all exchanges use a
+ routing key - refer to the specific exchange
+ documentation. If the routing key is empty and the
+ queue name is empty, the routing key will be the
+ current queue for the channel, which is the last
+ declared queue.
+
+ nowait: boolean
+
+ do not send a reply method
+
+ If set, the server will not respond to the method. The
+ client should not wait for a reply method. If the
+ server could not complete the method it will raise a
+ channel or connection exception.
+
+ arguments: table
+
+ arguments for binding
+
+ A set of arguments for the binding. The syntax and
+ semantics of these arguments depends on the exchange
+ class.
+ """
+ arguments = {} if arguments is None else arguments
+ args = AMQPWriter()
+ args.write_short(0)
+ args.write_shortstr(queue)
+ args.write_shortstr(exchange)
+ args.write_shortstr(routing_key)
+ args.write_bit(nowait)
+ args.write_table(arguments)
+ self._send_method((50, 20), args)
+
+ if not nowait:
+ return self.wait(allowed_methods=[
+ (50, 21), # Channel.queue_bind_ok
+ ])
+
+ def _queue_bind_ok(self, args):
+ """Confirm bind successful
+
+ This method confirms that the bind was successful.
+
+ """
+ pass
+
+ def queue_unbind(self, queue, exchange, routing_key='',
+ nowait=False, arguments=None):
+ """Unbind a queue from an exchange
+
+ This method unbinds a queue from an exchange.
+
+ RULE:
+
+ If a unbind fails, the server MUST raise a connection exception.
+
+ PARAMETERS:
+ queue: shortstr
+
+ Specifies the name of the queue to unbind.
+
+ RULE:
+
+ The client MUST either specify a queue name or have
+ previously declared a queue on the same channel
+
+ RULE:
+
+ The client MUST NOT attempt to unbind a queue that
+ does not exist.
+
+ exchange: shortstr
+
+ The name of the exchange to unbind from.
+
+ RULE:
+
+ The client MUST NOT attempt to unbind a queue from an
+ exchange that does not exist.
+
+ RULE:
+
+ The server MUST accept a blank exchange name to mean
+ the default exchange.
+
+ routing_key: shortstr
+
+ routing key of binding
+
+ Specifies the routing key of the binding to unbind.
+
+ arguments: table
+
+ arguments of binding
+
+ Specifies the arguments of the binding to unbind.
+
+ """
+ arguments = {} if arguments is None else arguments
+ args = AMQPWriter()
+ args.write_short(0)
+ args.write_shortstr(queue)
+ args.write_shortstr(exchange)
+ args.write_shortstr(routing_key)
+ #args.write_bit(nowait)
+ args.write_table(arguments)
+ self._send_method((50, 50), args)
+
+ if not nowait:
+ return self.wait(allowed_methods=[
+ (50, 51), # Channel.queue_unbind_ok
+ ])
+
+ def _queue_unbind_ok(self, args):
+ """Confirm unbind successful
+
+ This method confirms that the unbind was successful.
+
+ """
+ pass
+
+ def queue_declare(self, queue='', passive=False, durable=False,
+ exclusive=False, auto_delete=True, nowait=False,
+ arguments=None):
+ """Declare queue, create if needed
+
+ 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.
+
+ RULE:
+
+ The server MUST create a default binding for a newly-
+ created queue to the default exchange, which is an
+ exchange of type 'direct'.
+
+ RULE:
+
+ The server SHOULD support a minimum of 256 queues per
+ virtual host and ideally, impose no limit except as
+ defined by available resources.
+
+ PARAMETERS:
+ queue: shortstr
+
+ RULE:
+
+ The queue name MAY be empty, in which case the
+ server MUST create a new queue with a unique
+ generated name and return this to the client in
+ the Declare-Ok method.
+
+ RULE:
+
+ Queue names starting with "amq." are reserved for
+ predeclared and standardised server queues. If
+ the queue name starts with "amq." and the passive
+ option is False, the server MUST raise a connection
+ exception with reply code 403 (access refused).
+
+ passive: boolean
+
+ do not create queue
+
+ If set, the server will not create the queue. The
+ client can use this to check whether a queue exists
+ without modifying the server state.
+
+ RULE:
+
+ If set, and the queue does not already exist, the
+ server MUST respond with a reply code 404 (not
+ found) and raise a channel exception.
+
+ durable: boolean
+
+ request a durable queue
+
+ If set when creating a new queue, the queue will be
+ marked as durable. Durable queues remain active when
+ a server restarts. Non-durable queues (transient
+ queues) are purged if/when a server restarts. Note
+ that durable queues do not necessarily hold persistent
+ messages, although it does not make sense to send
+ persistent messages to a transient queue.
+
+ RULE:
+
+ The server MUST recreate the durable queue after a
+ restart.
+
+ RULE:
+
+ The server MUST support both durable and transient
+ queues.
+
+ RULE:
+
+ The server MUST ignore the durable field if the
+ queue already exists.
+
+ exclusive: boolean
+
+ request an exclusive queue
+
+ Exclusive queues may only be consumed from by the
+ current connection. Setting the 'exclusive' flag
+ always implies 'auto-delete'.
+
+ RULE:
+
+ The server MUST support both exclusive (private)
+ and non-exclusive (shared) queues.
+
+ RULE:
+
+ The server MUST raise a channel exception if
+ 'exclusive' is specified and the queue already
+ exists and is owned by a different connection.
+
+ auto_delete: boolean
+
+ auto-delete queue when unused
+
+ If set, the queue is deleted when all consumers have
+ finished using it. Last consumer can be cancelled
+ either explicitly or because its channel is closed. If
+ there was no consumer ever on the queue, it won't be
+ deleted.
+
+ RULE:
+
+ The server SHOULD allow for a reasonable delay
+ between the point when it determines that a queue
+ is not being used (or no longer used), and the
+ point when it deletes the queue. At the least it
+ must allow a client to create a queue and then
+ create a consumer to read from it, with a small
+ but non-zero delay between these two actions. The
+ server should equally allow for clients that may
+ be disconnected prematurely, and wish to re-
+ consume from the same queue without losing
+ messages. We would recommend a configurable
+ timeout, with a suitable default value being one
+ minute.
+
+ RULE:
+
+ The server MUST ignore the auto-delete field if
+ the queue already exists.
+
+ nowait: boolean
+
+ do not send a reply method
+
+ If set, the server will not respond to the method. The
+ client should not wait for a reply method. If the
+ server could not complete the method it will raise a
+ channel or connection exception.
+
+ arguments: table
+
+ arguments for declaration
+
+ A set of arguments for the declaration. The syntax and
+ semantics of these arguments depends on the server
+ implementation. This field is ignored if passive is
+ True.
+
+ Returns a tuple containing 3 items:
+ the name of the queue (essential for automatically-named queues)
+ message count
+ consumer count
+
+ """
+ arguments = {} if arguments is None else arguments
+ args = AMQPWriter()
+ args.write_short(0)
+ args.write_shortstr(queue)
+ args.write_bit(passive)
+ args.write_bit(durable)
+ args.write_bit(exclusive)
+ args.write_bit(auto_delete)
+ args.write_bit(nowait)
+ args.write_table(arguments)
+ self._send_method((50, 10), args)
+
+ if not nowait:
+ return self.wait(allowed_methods=[
+ (50, 11), # Channel.queue_declare_ok
+ ])
+
+ def _queue_declare_ok(self, args):
+ """Confirms a queue definition
+
+ This method confirms a Declare method and confirms the name of
+ the queue, essential for automatically-named queues.
+
+ PARAMETERS:
+ queue: shortstr
+
+ Reports the name of the queue. If the server generated
+ a queue name, this field contains that name.
+
+ message_count: long
+
+ number of messages in queue
+
+ Reports the number of messages in the queue, which
+ will be zero for newly-created queues.
+
+ consumer_count: long
+
+ number of consumers
+
+ Reports the number of active consumers for the queue.
+ Note that consumers can suspend activity
+ (Channel.Flow) in which case they do not appear in
+ this count.
+
+ """
+ return queue_declare_ok_t(
+ args.read_shortstr(),
+ args.read_long(),
+ args.read_long(),
+ )
+
+ def queue_delete(self, queue='',
+ if_unused=False, if_empty=False, nowait=False):
+ """Delete a 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.
+
+ RULE:
+
+ The server SHOULD use a dead-letter queue to hold messages
+ that were pending on a deleted queue, and MAY provide
+ facilities for a system administrator to move these
+ messages back to an active queue.
+
+ PARAMETERS:
+ queue: shortstr
+
+ Specifies the name of the queue to delete. If the
+ queue name is empty, refers to the current queue for
+ the channel, which is the last declared queue.
+
+ RULE:
+
+ If the client did not previously declare a queue,
+ and the queue name in this method is empty, the
+ server MUST raise a connection exception with
+ reply code 530 (not allowed).
+
+ RULE:
+
+ The queue must exist. Attempting to delete a non-
+ existing queue causes a channel exception.
+
+ if_unused: boolean
+
+ delete only if unused
+
+ If set, the server will only delete the queue if it
+ has no consumers. If the queue has consumers the
+ server does does not delete it but raises a channel
+ exception instead.
+
+ RULE:
+
+ The server MUST respect the if-unused flag when
+ deleting a queue.
+
+ if_empty: boolean
+
+ delete only if empty
+
+ If set, the server will only delete the queue if it
+ has no messages. If the queue is not empty the server
+ raises a channel exception.
+
+ nowait: boolean
+
+ do not send a reply method
+
+ If set, the server will not respond to the method. The
+ client should not wait for a reply method. If the
+ server could not complete the method it will raise a
+ channel or connection exception.
+
+ """
+ args = AMQPWriter()
+ args.write_short(0)
+ args.write_shortstr(queue)
+ args.write_bit(if_unused)
+ args.write_bit(if_empty)
+ args.write_bit(nowait)
+ self._send_method((50, 40), args)
+
+ if not nowait:
+ return self.wait(allowed_methods=[
+ (50, 41), # Channel.queue_delete_ok
+ ])
+
+ def _queue_delete_ok(self, args):
+ """Confirm deletion of a queue
+
+ This method confirms the deletion of a queue.
+
+ PARAMETERS:
+ message_count: long
+
+ number of messages purged
+
+ Reports the number of messages purged.
+
+ """
+ return args.read_long()
+
+ def queue_purge(self, queue='', nowait=False):
+ """Purge a queue
+
+ This method removes all messages from a queue. It does not
+ cancel consumers. Purged messages are deleted without any
+ formal "undo" mechanism.
+
+ RULE:
+
+ A call to purge MUST result in an empty queue.
+
+ RULE:
+
+ On transacted channels the server MUST not purge messages
+ that have already been sent to a client but not yet
+ acknowledged.
+
+ RULE:
+
+ The server MAY implement a purge queue or log that allows
+ system administrators to recover accidentally-purged
+ messages. The server SHOULD NOT keep purged messages in
+ the same storage spaces as the live messages since the
+ volumes of purged messages may get very large.
+
+ PARAMETERS:
+ queue: shortstr
+
+ Specifies the name of the queue to purge. If the
+ queue name is empty, refers to the current queue for
+ the channel, which is the last declared queue.
+
+ RULE:
+
+ If the client did not previously declare a queue,
+ and the queue name in this method is empty, the
+ server MUST raise a connection exception with
+ reply code 530 (not allowed).
+
+ RULE:
+
+ The queue must exist. Attempting to purge a non-
+ existing queue causes a channel exception.
+
+ nowait: boolean
+
+ do not send a reply method
+
+ If set, the server will not respond to the method. The
+ client should not wait for a reply method. If the
+ server could not complete the method it will raise a
+ channel or connection exception.
+
+ if nowait is False, returns a message_count
+
+ """
+ args = AMQPWriter()
+ args.write_short(0)
+ args.write_shortstr(queue)
+ args.write_bit(nowait)
+ self._send_method((50, 30), args)
+
+ if not nowait:
+ return self.wait(allowed_methods=[
+ (50, 31), # Channel.queue_purge_ok
+ ])
+
+ def _queue_purge_ok(self, args):
+ """Confirms a queue purge
+
+ This method confirms the purge of a queue.
+
+ PARAMETERS:
+ message_count: long
+
+ number of messages purged
+
+ Reports the number of messages purged.
+
+ """
+ return args.read_long()
+
+ #############
+ #
+ # Basic
+ #
+ #
+ # work with basic content
+ #
+ # The Basic class provides methods that support an industry-
+ # standard messaging model.
+ #
+ # GRAMMAR::
+ #
+ # basic = C:QOS S:QOS-OK
+ # / C:CONSUME S:CONSUME-OK
+ # / C:CANCEL S:CANCEL-OK
+ # / C:PUBLISH content
+ # / S:RETURN content
+ # / S:DELIVER content
+ # / C:GET ( S:GET-OK content / S:GET-EMPTY )
+ # / C:ACK
+ # / C:REJECT
+ #
+ # RULE:
+ #
+ # The server SHOULD respect the persistent property of basic
+ # messages and SHOULD make a best-effort to hold persistent
+ # basic messages on a reliable storage mechanism.
+ #
+ # RULE:
+ #
+ # The server MUST NOT discard a persistent basic message in
+ # case of a queue overflow. The server MAY use the
+ # Channel.Flow method to slow or stop a basic message
+ # publisher when necessary.
+ #
+ # RULE:
+ #
+ # The server MAY overflow non-persistent basic messages to
+ # persistent storage and MAY discard or dead-letter non-
+ # persistent basic messages on a priority basis if the queue
+ # size exceeds some configured limit.
+ #
+ # RULE:
+ #
+ # The server MUST implement at least 2 priority levels for
+ # basic messages, where priorities 0-4 and 5-9 are treated as
+ # two distinct levels. The server MAY implement up to 10
+ # priority levels.
+ #
+ # RULE:
+ #
+ # The server MUST deliver messages of the same priority in
+ # order irrespective of their individual persistence.
+ #
+ # RULE:
+ #
+ # The server MUST support both automatic and explicit
+ # acknowledgements on Basic content.
+ #
+
+ def basic_ack(self, delivery_tag, multiple=False):
+ """Acknowledge one or more messages
+
+ 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: longlong
+
+ server-assigned delivery tag
+
+ The server-assigned and channel-specific delivery tag
+
+ RULE:
+
+ The delivery tag is valid only within the channel
+ from which the message was received. I.e. a client
+ MUST NOT receive a message on one channel and then
+ acknowledge it on another.
+
+ RULE:
+
+ The server MUST NOT use a zero value for delivery
+ tags. Zero is reserved for client use, meaning "all
+ messages so far received".
+
+ multiple: boolean
+
+ acknowledge multiple messages
+
+ If set to True, the delivery tag is treated as "up to
+ and including", so that the client can acknowledge
+ multiple messages with a single method. If set to
+ False, the delivery tag refers to a single message.
+ If the multiple field is True, and the delivery tag
+ is zero, tells the server to acknowledge all
+ outstanding mesages.
+
+ RULE:
+
+ The server MUST validate that a non-zero delivery-
+ tag refers to an delivered message, and raise a
+ channel exception if this is not the case.
+
+ """
+ args = AMQPWriter()
+ args.write_longlong(delivery_tag)
+ args.write_bit(multiple)
+ self._send_method((60, 80), args)
+
+ def basic_cancel(self, consumer_tag, nowait=False):
+ """End a queue consumer
+
+ 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 abitrary number of messages in between sending the cancel
+ method and receiving the cancel-ok reply.
+
+ RULE:
+
+ If the queue no longer exists when the client sends a
+ cancel command, or the consumer has been cancelled for
+ other reasons, this command has no effect.
+
+ PARAMETERS:
+ consumer_tag: shortstr
+
+ consumer tag
+
+ Identifier for the consumer, valid within the current
+ connection.
+
+ RULE:
+
+ The consumer tag is valid only within the channel
+ from which the consumer was created. I.e. a client
+ MUST NOT create a consumer in one channel and then
+ use it in another.
+
+ nowait: boolean
+
+ do not send a reply method
+
+ If set, the server will not respond to the method. The
+ client should not wait for a reply method. If the
+ server could not complete the method it will raise a
+ channel or connection exception.
+
+ """
+ if self.connection is not None:
+ self.no_ack_consumers.discard(consumer_tag)
+ args = AMQPWriter()
+ args.write_shortstr(consumer_tag)
+ args.write_bit(nowait)
+ self._send_method((60, 30), args)
+ return self.wait(allowed_methods=[
+ (60, 31), # Channel.basic_cancel_ok
+ ])
+
+ def _basic_cancel_notify(self, args):
+ """Consumer cancelled by server.
+
+ Most likely the queue was deleted.
+
+ """
+ consumer_tag = args.read_shortstr()
+ callback = self._on_cancel(consumer_tag)
+ if callback:
+ callback(consumer_tag)
+ else:
+ raise ConsumerCancelled(consumer_tag, (60, 30))
+
+ def _basic_cancel_ok(self, args):
+ """Confirm a cancelled consumer
+
+ This method confirms that the cancellation was completed.
+
+ PARAMETERS:
+ consumer_tag: shortstr
+
+ consumer tag
+
+ Identifier for the consumer, valid within the current
+ connection.
+
+ RULE:
+
+ The consumer tag is valid only within the channel
+ from which the consumer was created. I.e. a client
+ MUST NOT create a consumer in one channel and then
+ use it in another.
+
+ """
+ consumer_tag = args.read_shortstr()
+ self._on_cancel(consumer_tag)
+
+ def _on_cancel(self, consumer_tag):
+ self.callbacks.pop(consumer_tag, None)
+ return self.cancel_callbacks.pop(consumer_tag, None)
+
+ def basic_consume(self, queue='', consumer_tag='', no_local=False,
+ no_ack=False, exclusive=False, nowait=False,
+ callback=None, arguments=None, on_cancel=None):
+ """Start a queue consumer
+
+ 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 created on, or
+ until the client cancels them.
+
+ RULE:
+
+ The server SHOULD support at least 16 consumers per queue,
+ unless the queue was declared as private, and ideally,
+ impose no limit except as defined by available resources.
+
+ PARAMETERS:
+ queue: shortstr
+
+ Specifies the name of the queue to consume from. If
+ the queue name is null, refers to the current queue
+ for the channel, which is the last declared queue.
+
+ RULE:
+
+ If the client did not previously declare a queue,
+ and the queue name in this method is empty, the
+ server MUST raise a connection exception with
+ reply code 530 (not allowed).
+
+ consumer_tag: shortstr
+
+ Specifies the identifier for the consumer. The
+ consumer tag is local to a connection, so two clients
+ can use the same consumer tags. If this field is empty
+ the server will generate a unique tag.
+
+ RULE:
+
+ The tag MUST NOT refer to an existing consumer. If
+ the client attempts to create two consumers with
+ the same non-empty tag the server MUST raise a
+ connection exception with reply code 530 (not
+ allowed).
+
+ no_local: boolean
+
+ do not deliver own messages
+
+ If the no-local field is set the server will not send
+ messages to the client that published them.
+
+ no_ack: boolean
+
+ no acknowledgement needed
+
+ If this field is set the server does not expect
+ acknowledgments for messages. That is, when a message
+ is delivered to the client the server automatically and
+ silently acknowledges it on behalf of the client. This
+ functionality increases performance but at the cost of
+ reliability. Messages can get lost if a client dies
+ before it can deliver them to the application.
+
+ exclusive: boolean
+
+ request exclusive access
+
+ Request exclusive consumer access, meaning only this
+ consumer can access the queue.
+
+ RULE:
+
+ If the server cannot grant exclusive access to the
+ queue when asked, - because there are other
+ consumers active - it MUST raise a channel
+ exception with return code 403 (access refused).
+
+ nowait: boolean
+
+ do not send a reply method
+
+ If set, the server will not respond to the method. The
+ client should not wait for a reply method. If the
+ server could not complete the method it will raise a
+ channel or connection exception.
+
+ callback: Python callable
+
+ function/method called with each delivered message
+
+ For each message delivered by the broker, the
+ callable will be called with a Message object
+ as the single argument. If no callable is specified,
+ messages are quietly discarded, no_ack should probably
+ be set to True in that case.
+
+ """
+ args = AMQPWriter()
+ args.write_short(0)
+ args.write_shortstr(queue)
+ args.write_shortstr(consumer_tag)
+ args.write_bit(no_local)
+ args.write_bit(no_ack)
+ args.write_bit(exclusive)
+ args.write_bit(nowait)
+ args.write_table(arguments or {})
+ self._send_method((60, 20), args)
+
+ if not nowait:
+ consumer_tag = self.wait(allowed_methods=[
+ (60, 21), # Channel.basic_consume_ok
+ ])
+
+ self.callbacks[consumer_tag] = callback
+
+ if on_cancel:
+ self.cancel_callbacks[consumer_tag] = on_cancel
+ if no_ack:
+ self.no_ack_consumers.add(consumer_tag)
+
+ return consumer_tag
+
+ def _basic_consume_ok(self, args):
+ """Confirm a new consumer
+
+ The server provides the client with a consumer tag, which is
+ used by the client for methods called on the consumer at a
+ later stage.
+
+ PARAMETERS:
+ consumer_tag: shortstr
+
+ Holds the consumer tag specified by the client or
+ provided by the server.
+
+ """
+ return args.read_shortstr()
+
+ def _basic_deliver(self, args, msg):
+ """Notify the client of a consumer message
+
+ This method delivers a message to the client, via a consumer.
+ In the asynchronous message delivery model, the client starts
+ a consumer using the Consume method, then the server responds
+ with Deliver methods as and when messages arrive for that
+ consumer.
+
+ RULE:
+
+ The server SHOULD track the number of times a message has
+ been delivered to clients and when a message is
+ redelivered a certain number of times - e.g. 5 times -
+ without being acknowledged, the server SHOULD consider the
+ message to be unprocessable (possibly causing client
+ applications to abort), and move the message to a dead
+ letter queue.
+
+ PARAMETERS:
+ consumer_tag: shortstr
+
+ consumer tag
+
+ Identifier for the consumer, valid within the current
+ connection.
+
+ RULE:
+
+ The consumer tag is valid only within the channel
+ from which the consumer was created. I.e. a client
+ MUST NOT create a consumer in one channel and then
+ use it in another.
+
+ delivery_tag: longlong
+
+ server-assigned delivery tag
+
+ The server-assigned and channel-specific delivery tag
+
+ RULE:
+
+ The delivery tag is valid only within the channel
+ from which the message was received. I.e. a client
+ MUST NOT receive a message on one channel and then
+ acknowledge it on another.
+
+ RULE:
+
+ The server MUST NOT use a zero value for delivery
+ tags. Zero is reserved for client use, meaning "all
+ messages so far received".
+
+ redelivered: boolean
+
+ message is being redelivered
+
+ This indicates that the message has been previously
+ delivered to this or another client.
+
+ exchange: shortstr
+
+ Specifies the name of the exchange that the message
+ was originally published to.
+
+ routing_key: shortstr
+
+ Message routing key
+
+ Specifies the routing key name specified when the
+ message was published.
+
+ """
+ consumer_tag = args.read_shortstr()
+ delivery_tag = args.read_longlong()
+ redelivered = args.read_bit()
+ exchange = args.read_shortstr()
+ routing_key = args.read_shortstr()
+
+ msg.channel = self
+ msg.delivery_info = {
+ 'consumer_tag': consumer_tag,
+ 'delivery_tag': delivery_tag,
+ 'redelivered': redelivered,
+ 'exchange': exchange,
+ 'routing_key': routing_key,
+ }
+
+ try:
+ fun = self.callbacks[consumer_tag]
+ except KeyError:
+ pass
+ else:
+ fun(msg)
+
+ def basic_get(self, queue='', no_ack=False):
+ """Direct access to a queue
+
+ 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.
+
+ PARAMETERS:
+ queue: shortstr
+
+ Specifies the name of the queue to consume from. If
+ the queue name is null, refers to the current queue
+ for the channel, which is the last declared queue.
+
+ RULE:
+
+ If the client did not previously declare a queue,
+ and the queue name in this method is empty, the
+ server MUST raise a connection exception with
+ reply code 530 (not allowed).
+
+ no_ack: boolean
+
+ no acknowledgement needed
+
+ If this field is set the server does not expect
+ acknowledgments for messages. That is, when a message
+ is delivered to the client the server automatically and
+ silently acknowledges it on behalf of the client. This
+ functionality increases performance but at the cost of
+ reliability. Messages can get lost if a client dies
+ before it can deliver them to the application.
+
+ Non-blocking, returns a message object, or None.
+
+ """
+ args = AMQPWriter()
+ args.write_short(0)
+ args.write_shortstr(queue)
+ args.write_bit(no_ack)
+ self._send_method((60, 70), args)
+ return self.wait(allowed_methods=[
+ (60, 71), # Channel.basic_get_ok
+ (60, 72), # Channel.basic_get_empty
+ ])
+
+ def _basic_get_empty(self, args):
+ """Indicate no messages available
+
+ This method tells the client that the queue has no messages
+ available for the client.
+
+ PARAMETERS:
+ cluster_id: shortstr
+
+ Cluster id
+
+ For use by cluster applications, should not be used by
+ client applications.
+
+ """
+ cluster_id = args.read_shortstr() # noqa
+
+ def _basic_get_ok(self, args, msg):
+ """Provide client with a message
+
+ This method delivers a message to the client following a get
+ method. A message delivered by 'get-ok' must be acknowledged
+ unless the no-ack option was set in the get method.
+
+ PARAMETERS:
+ delivery_tag: longlong
+
+ server-assigned delivery tag
+
+ The server-assigned and channel-specific delivery tag
+
+ RULE:
+
+ The delivery tag is valid only within the channel
+ from which the message was received. I.e. a client
+ MUST NOT receive a message on one channel and then
+ acknowledge it on another.
+
+ RULE:
+
+ The server MUST NOT use a zero value for delivery
+ tags. Zero is reserved for client use, meaning "all
+ messages so far received".
+
+ redelivered: boolean
+
+ message is being redelivered
+
+ This indicates that the message has been previously
+ delivered to this or another client.
+
+ exchange: shortstr
+
+ Specifies the name of the exchange that the message
+ was originally published to. If empty, the message
+ was published to the default exchange.
+
+ routing_key: shortstr
+
+ Message routing key
+
+ Specifies the routing key name specified when the
+ message was published.
+
+ message_count: long
+
+ number of messages pending
+
+ This field reports the number of messages pending on
+ the queue, excluding the message being delivered.
+ Note that this figure is indicative, not reliable, and
+ can change arbitrarily as messages are added to the
+ queue and removed by other clients.
+
+ """
+ delivery_tag = args.read_longlong()
+ redelivered = args.read_bit()
+ exchange = args.read_shortstr()
+ routing_key = args.read_shortstr()
+ message_count = args.read_long()
+
+ msg.channel = self
+ msg.delivery_info = {
+ 'delivery_tag': delivery_tag,
+ 'redelivered': redelivered,
+ 'exchange': exchange,
+ 'routing_key': routing_key,
+ 'message_count': message_count
+ }
+ return msg
+
+ def _basic_publish(self, msg, exchange='', routing_key='',
+ mandatory=False, immediate=False):
+ """Publish a message
+
+ 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.
+
+ PARAMETERS:
+ exchange: shortstr
+
+ Specifies the name of the exchange to publish to. The
+ exchange name can be empty, meaning the default
+ exchange. If the exchange name is specified, and that
+ exchange does not exist, the server will raise a
+ channel exception.
+
+ RULE:
+
+ The server MUST accept a blank exchange name to
+ mean the default exchange.
+
+ RULE:
+
+ The exchange MAY refuse basic content in which
+ case it MUST raise a channel exception with reply
+ code 540 (not implemented).
+
+ routing_key: shortstr
+
+ Message routing key
+
+ Specifies the routing key for the message. The
+ routing key is used for routing messages depending on
+ the exchange configuration.
+
+ mandatory: boolean
+
+ indicate mandatory routing
+
+ This flag tells the server how to react if the message
+ cannot be routed to a queue. If this flag is True, the
+ server will return an unroutable message with a Return
+ method. If this flag is False, the server silently
+ drops the message.
+
+ RULE:
+
+ The server SHOULD implement the mandatory flag.
+
+ immediate: boolean
+
+ request immediate delivery
+
+ This flag tells the server how to react if the message
+ cannot be routed to a queue consumer immediately. If
+ this flag is set, the server will return an
+ undeliverable message with a Return method. If this
+ flag is zero, the server will queue the message, but
+ with no guarantee that it will ever be consumed.
+
+ RULE:
+
+ The server SHOULD implement the immediate flag.
+
+ """
+ args = AMQPWriter()
+ args.write_short(0)
+ args.write_shortstr(exchange)
+ args.write_shortstr(routing_key)
+ args.write_bit(mandatory)
+ args.write_bit(immediate)
+
+ self._send_method((60, 40), args, msg)
+ basic_publish = _basic_publish
+
+ def basic_publish_confirm(self, *args, **kwargs):
+ if not self._confirm_selected:
+ self._confirm_selected = True
+ self.confirm_select()
+ ret = self._basic_publish(*args, **kwargs)
+ self.wait([(60, 80)])
+ return ret
+
+ def basic_qos(self, prefetch_size, prefetch_count, a_global):
+ """Specify quality of service
+
+ 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: long
+
+ prefetch window in octets
+
+ The client can request that messages be sent in
+ advance so that when the client finishes processing a
+ message, the following message is already held
+ locally, rather than needing to be sent down the
+ channel. Prefetching gives a performance improvement.
+ This field specifies the prefetch window size in
+ octets. The server will send a message in advance if
+ it is equal to or smaller in size than the available
+ prefetch size (and also falls into other prefetch
+ limits). May be set to zero, meaning "no specific
+ limit", although other prefetch limits may still
+ apply. The prefetch-size is ignored if the no-ack
+ option is set.
+
+ RULE:
+
+ The server MUST ignore this setting when the
+ client is not processing any messages - i.e. the
+ prefetch size does not limit the transfer of
+ single messages to a client, only the sending in
+ advance of more messages while the client still
+ has one or more unacknowledged messages.
+
+ prefetch_count: short
+
+ prefetch window in messages
+
+ Specifies a prefetch window in terms of whole
+ messages. This field may be used in combination with
+ the prefetch-size field; a message will only be sent
+ in advance if both prefetch windows (and those at the
+ channel and connection level) allow it. The prefetch-
+ count is ignored if the no-ack option is set.
+
+ RULE:
+
+ The server MAY send less data in advance than
+ allowed by the client's specified prefetch windows
+ but it MUST NOT send more.
+
+ a_global: boolean
+
+ apply to entire connection
+
+ By default the QoS settings apply to the current
+ channel only. If this field is set, they are applied
+ to the entire connection.
+
+ """
+ args = AMQPWriter()
+ args.write_long(prefetch_size)
+ args.write_short(prefetch_count)
+ args.write_bit(a_global)
+ self._send_method((60, 10), args)
+ return self.wait(allowed_methods=[
+ (60, 11), # Channel.basic_qos_ok
+ ])
+
+ def _basic_qos_ok(self, args):
+ """Confirm the requested qos
+
+ This method tells the client that the requested QoS levels
+ could be handled by the server. The requested QoS applies to
+ all active consumers until a new QoS is defined.
+
+ """
+ pass
+
+ def basic_recover(self, requeue=False):
+ """Redeliver unacknowledged messages
+
+ This method asks the broker to redeliver all unacknowledged
+ messages on a specified channel. Zero or more messages may be
+ redelivered. This method is only allowed on non-transacted
+ channels.
+
+ RULE:
+
+ The server MUST set the redelivered flag on all messages
+ that are resent.
+
+ RULE:
+
+ The server MUST raise a channel exception if this is
+ called on a transacted channel.
+
+ PARAMETERS:
+ requeue: boolean
+
+ requeue the message
+
+ If this field is False, the message will be redelivered
+ to the original recipient. If this field is True, the
+ server will attempt to requeue the message,
+ potentially then delivering it to an alternative
+ subscriber.
+
+ """
+ args = AMQPWriter()
+ args.write_bit(requeue)
+ self._send_method((60, 110), args)
+
+ def basic_recover_async(self, requeue=False):
+ args = AMQPWriter()
+ args.write_bit(requeue)
+ self._send_method((60, 100), args)
+
+ def _basic_recover_ok(self, args):
+ """In 0-9-1 the deprecated recover solicits a response."""
+ pass
+
+ def basic_reject(self, delivery_tag, requeue):
+ """Reject an incoming message
+
+ 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.
+
+ RULE:
+
+ The server SHOULD be capable of accepting and process the
+ Reject method while sending message content with a Deliver
+ or Get-Ok method. I.e. the server should read and process
+ incoming methods while sending output frames. To cancel a
+ partially-send content, the server sends a content body
+ frame of size 1 (i.e. with no data except the frame-end
+ octet).
+
+ RULE:
+
+ The server SHOULD interpret this method as meaning that
+ the client is unable to process the message at this time.
+
+ RULE:
+
+ A client MUST NOT use this method as a means of selecting
+ messages to process. A rejected message MAY be discarded
+ or dead-lettered, not necessarily passed to another
+ client.
+
+ PARAMETERS:
+ delivery_tag: longlong
+
+ server-assigned delivery tag
+
+ The server-assigned and channel-specific delivery tag
+
+ RULE:
+
+ The delivery tag is valid only within the channel
+ from which the message was received. I.e. a client
+ MUST NOT receive a message on one channel and then
+ acknowledge it on another.
+
+ RULE:
+
+ The server MUST NOT use a zero value for delivery
+ tags. Zero is reserved for client use, meaning "all
+ messages so far received".
+
+ requeue: boolean
+
+ requeue the message
+
+ If this field is False, the message will be discarded.
+ If this field is True, the server will attempt to
+ requeue the message.
+
+ RULE:
+
+ The server MUST NOT deliver the message to the
+ same client within the context of the current
+ channel. The recommended strategy is to attempt
+ to deliver the message to an alternative consumer,
+ and if that is not possible, to move the message
+ to a dead-letter queue. The server MAY use more
+ sophisticated tracking to hold the message on the
+ queue and redeliver it to the same client at a
+ later stage.
+
+ """
+ args = AMQPWriter()
+ args.write_longlong(delivery_tag)
+ args.write_bit(requeue)
+ self._send_method((60, 90), args)
+
+ def _basic_return(self, args, msg):
+ """Return a failed message
+
+ This method returns an undeliverable message that was
+ published with the "immediate" flag set, or an unroutable
+ message published with the "mandatory" flag set. The reply
+ code and text provide information about the reason that the
+ message was undeliverable.
+
+ PARAMETERS:
+ reply_code: short
+
+ The reply code. The AMQ reply codes are defined in AMQ
+ RFC 011.
+
+ reply_text: shortstr
+
+ The localised reply text. This text can be logged as an
+ aid to resolving issues.
+
+ exchange: shortstr
+
+ Specifies the name of the exchange that the message
+ was originally published to.
+
+ routing_key: shortstr
+
+ Message routing key
+
+ Specifies the routing key name specified when the
+ message was published.
+
+ """
+ self.returned_messages.put(basic_return_t(
+ args.read_short(),
+ args.read_shortstr(),
+ args.read_shortstr(),
+ args.read_shortstr(),
+ msg,
+ ))
+
+ #############
+ #
+ # Tx
+ #
+ #
+ # work with standard transactions
+ #
+ # Standard transactions provide so-called "1.5 phase commit". We
+ # can ensure that work is never lost, but there is a chance of
+ # confirmations being lost, so that messages may be resent.
+ # Applications that use standard transactions must be able to
+ # detect and ignore duplicate messages.
+ #
+ # GRAMMAR::
+ #
+ # tx = C:SELECT S:SELECT-OK
+ # / C:COMMIT S:COMMIT-OK
+ # / C:ROLLBACK S:ROLLBACK-OK
+ #
+ # RULE:
+ #
+ # An client using standard transactions SHOULD be able to
+ # track all messages received within a reasonable period, and
+ # thus detect and reject duplicates of the same message. It
+ # SHOULD NOT pass these to the application layer.
+ #
+ #
+
+ def tx_commit(self):
+ """Commit the current transaction
+
+ This method commits all messages published and acknowledged in
+ the current transaction. A new transaction starts immediately
+ after a commit.
+
+ """
+ self._send_method((90, 20))
+ return self.wait(allowed_methods=[
+ (90, 21), # Channel.tx_commit_ok
+ ])
+
+ def _tx_commit_ok(self, args):
+ """Confirm a successful commit
+
+ This method confirms to the client that the commit succeeded.
+ Note that if a commit fails, the server raises a channel
+ exception.
+
+ """
+ pass
+
+ def tx_rollback(self):
+ """Abandon the current transaction
+
+ This method abandons all messages published and acknowledged
+ in the current transaction. A new transaction starts
+ immediately after a rollback.
+
+ """
+ self._send_method((90, 30))
+ return self.wait(allowed_methods=[
+ (90, 31), # Channel.tx_rollback_ok
+ ])
+
+ def _tx_rollback_ok(self, args):
+ """Confirm a successful rollback
+
+ This method confirms to the client that the rollback
+ succeeded. Note that if an rollback fails, the server raises a
+ channel exception.
+
+ """
+ pass
+
+ def tx_select(self):
+ """Select standard transaction mode
+
+ 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.
+
+ """
+ self._send_method((90, 10))
+ return self.wait(allowed_methods=[
+ (90, 11), # Channel.tx_select_ok
+ ])
+
+ def _tx_select_ok(self, args):
+ """Confirm transaction mode
+
+ This method confirms to the client that the channel was
+ successfully set to use standard transactions.
+
+ """
+ pass
+
+ def confirm_select(self, nowait=False):
+ """Enables publisher confirms for this channel (an RabbitMQ
+ extension).
+
+ Can now be used if the channel is in transactional mode.
+
+ :param nowait:
+ If set, the server will not respond to the method.
+ The client should not wait for a reply method. If the
+ server could not complete the method it will raise a channel
+ or connection exception.
+
+ """
+ args = AMQPWriter()
+ args.write_bit(nowait)
+
+ self._send_method((85, 10), args)
+ if not nowait:
+ self.wait(allowed_methods=[
+ (85, 11), # Confirm.select_ok
+ ])
+
+ def _confirm_select_ok(self, args):
+ """With this method the broker confirms to the client that
+ the channel is now using publisher confirms."""
+ pass
+
+ def _basic_ack_recv(self, args):
+ delivery_tag = args.read_longlong()
+ multiple = args.read_bit()
+ for callback in self.events['basic_ack']:
+ callback(delivery_tag, multiple)
+
+ _METHOD_MAP = {
+ (20, 11): _open_ok,
+ (20, 20): _flow,
+ (20, 21): _flow_ok,
+ (20, 40): _close,
+ (20, 41): _close_ok,
+ (40, 11): _exchange_declare_ok,
+ (40, 21): _exchange_delete_ok,
+ (40, 31): _exchange_bind_ok,
+ (40, 51): _exchange_unbind_ok,
+ (50, 11): _queue_declare_ok,
+ (50, 21): _queue_bind_ok,
+ (50, 31): _queue_purge_ok,
+ (50, 41): _queue_delete_ok,
+ (50, 51): _queue_unbind_ok,
+ (60, 11): _basic_qos_ok,
+ (60, 21): _basic_consume_ok,
+ (60, 30): _basic_cancel_notify,
+ (60, 31): _basic_cancel_ok,
+ (60, 50): _basic_return,
+ (60, 60): _basic_deliver,
+ (60, 71): _basic_get_ok,
+ (60, 72): _basic_get_empty,
+ (60, 80): _basic_ack_recv,
+ (60, 111): _basic_recover_ok,
+ (85, 11): _confirm_select_ok,
+ (90, 11): _tx_select_ok,
+ (90, 21): _tx_commit_ok,
+ (90, 31): _tx_rollback_ok,
+ }
+
+ _IMMEDIATE_METHODS = [
+ (60, 50), # basic_return
+ ]
diff --git a/amqp/connection.py b/amqp/connection.py
new file mode 100644
index 0000000..c93d91f
--- /dev/null
+++ b/amqp/connection.py
@@ -0,0 +1,1004 @@
+"""AMQP Connections"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+from __future__ import absolute_import
+
+import logging
+import socket
+
+from array import array
+try:
+ from ssl import SSLError
+except ImportError:
+ class SSLError(Exception): # noqa
+ pass
+
+from . import __version__
+from .abstract_channel import AbstractChannel
+from .channel import Channel
+from .exceptions import (
+ AMQPNotImplementedError, ChannelError, ResourceError,
+ ConnectionForced, ConnectionError, error_for_code,
+ RecoverableConnectionError, RecoverableChannelError,
+)
+from .five import items, range, values, monotonic
+from .method_framing import MethodReader, MethodWriter
+from .serialization import AMQPWriter
+from .transport import create_transport
+
+HAS_MSG_PEEK = hasattr(socket, 'MSG_PEEK')
+
+START_DEBUG_FMT = """
+Start from server, version: %d.%d, properties: %s, mechanisms: %s, locales: %s
+""".strip()
+
+__all__ = ['Connection']
+
+#
+# Client property info that gets sent to the server on connection startup
+#
+LIBRARY_PROPERTIES = {
+ 'product': 'py-amqp',
+ 'product_version': __version__,
+ 'capabilities': {},
+}
+
+AMQP_LOGGER = logging.getLogger('amqp')
+
+
+class Connection(AbstractChannel):
+ """The connection class provides methods for a client to establish a
+ network connection to a server, and for both peers to operate the
+ connection thereafter.
+
+ GRAMMAR::
+
+ connection = open-connection *use-connection close-connection
+ open-connection = C:protocol-header
+ S:START C:START-OK
+ *challenge
+ S:TUNE C:TUNE-OK
+ C:OPEN S:OPEN-OK
+ challenge = S:SECURE C:SECURE-OK
+ use-connection = *channel
+ close-connection = C:CLOSE S:CLOSE-OK
+ / S:CLOSE C:CLOSE-OK
+
+ """
+ Channel = Channel
+
+ #: Final heartbeat interval value (in float seconds) after negotiation
+ heartbeat = None
+
+ #: Original heartbeat interval value proposed by client.
+ client_heartbeat = None
+
+ #: Original heartbeat interval proposed by server.
+ server_heartbeat = None
+
+ #: Time of last heartbeat sent (in monotonic time, if available).
+ last_heartbeat_sent = 0
+
+ #: Time of last heartbeat received (in monotonic time, if available).
+ last_heartbeat_received = 0
+
+ #: Number of bytes sent to socket at the last heartbeat check.
+ prev_sent = None
+
+ #: Number of bytes received from socket at the last heartbeat check.
+ prev_recv = None
+
+ def __init__(self, host='localhost', userid='guest', password='guest',
+ login_method='AMQPLAIN', login_response=None,
+ virtual_host='/', locale='en_US', client_properties=None,
+ ssl=False, connect_timeout=None, channel_max=None,
+ frame_max=None, heartbeat=0, on_blocked=None,
+ on_unblocked=None, confirm_publish=False, **kwargs):
+ """Create a connection to the specified host, which should be
+ a 'host[:port]', such as 'localhost', or '1.2.3.4:5672'
+ (defaults to 'localhost', if a port is not specified then
+ 5672 is used)
+
+ If login_response is not specified, one is built up for you from
+ userid and password if they are present.
+
+ The 'ssl' parameter may be simply True/False, or for Python >= 2.6
+ a dictionary of options to pass to ssl.wrap_socket() such as
+ requiring certain certificates.
+
+ """
+ channel_max = channel_max or 65535
+ frame_max = frame_max or 131072
+ if (login_response is None) \
+ and (userid is not None) \
+ and (password is not None):
+ login_response = AMQPWriter()
+ login_response.write_table({'LOGIN': userid, 'PASSWORD': password})
+ login_response = login_response.getvalue()[4:] # Skip the length
+ # at the beginning
+
+ d = dict(LIBRARY_PROPERTIES, **client_properties or {})
+ self._method_override = {(60, 50): self._dispatch_basic_return}
+
+ self.channels = {}
+ # The connection object itself is treated as channel 0
+ super(Connection, self).__init__(self, 0)
+
+ self.transport = None
+
+ # Properties set in the Tune method
+ self.channel_max = channel_max
+ self.frame_max = frame_max
+ self.client_heartbeat = heartbeat
+
+ self.confirm_publish = confirm_publish
+
+ # Callbacks
+ self.on_blocked = on_blocked
+ self.on_unblocked = on_unblocked
+
+ self._avail_channel_ids = array('H', range(self.channel_max, 0, -1))
+
+ # Properties set in the Start method
+ self.version_major = 0
+ self.version_minor = 0
+ self.server_properties = {}
+ self.mechanisms = []
+ self.locales = []
+
+ # Let the transport.py module setup the actual
+ # socket connection to the broker.
+ #
+ self.transport = create_transport(host, connect_timeout, ssl)
+
+ self.method_reader = MethodReader(self.transport)
+ self.method_writer = MethodWriter(self.transport, self.frame_max)
+
+ self.wait(allowed_methods=[
+ (10, 10), # start
+ ])
+
+ self._x_start_ok(d, login_method, login_response, locale)
+
+ self._wait_tune_ok = True
+ while self._wait_tune_ok:
+ self.wait(allowed_methods=[
+ (10, 20), # secure
+ (10, 30), # tune
+ ])
+
+ return self._x_open(virtual_host)
+
+ @property
+ def connected(self):
+ return self.transport and self.transport.connected
+
+ def _do_close(self):
+ try:
+ self.transport.close()
+
+ temp_list = [x for x in values(self.channels) if x is not self]
+ for ch in temp_list:
+ ch._do_close()
+ except socket.error:
+ pass # connection already closed on the other end
+ finally:
+ self.transport = self.connection = self.channels = None
+
+ def _get_free_channel_id(self):
+ try:
+ return self._avail_channel_ids.pop()
+ except IndexError:
+ raise ResourceError(
+ 'No free channel ids, current={0}, channel_max={1}'.format(
+ len(self.channels), self.channel_max), (20, 10))
+
+ def _claim_channel_id(self, channel_id):
+ try:
+ return self._avail_channel_ids.remove(channel_id)
+ except ValueError:
+ raise ConnectionError(
+ 'Channel %r already open' % (channel_id, ))
+
+ def _wait_method(self, channel_id, allowed_methods):
+ """Wait for a method from the server destined for
+ a particular channel."""
+ #
+ # Check the channel's deferred methods
+ #
+ method_queue = self.channels[channel_id].method_queue
+
+ for queued_method in method_queue:
+ method_sig = queued_method[0]
+ if (allowed_methods is None) \
+ or (method_sig in allowed_methods) \
+ or (method_sig == (20, 40)):
+ method_queue.remove(queued_method)
+ return queued_method
+
+ #
+ # Nothing queued, need to wait for a method from the peer
+ #
+ while 1:
+ channel, method_sig, args, content = \
+ self.method_reader.read_method()
+
+ if channel == channel_id and (
+ allowed_methods is None or
+ method_sig in allowed_methods or
+ method_sig == (20, 40)):
+ return method_sig, args, content
+
+ #
+ # Certain methods like basic_return should be dispatched
+ # immediately rather than being queued, even if they're not
+ # one of the 'allowed_methods' we're looking for.
+ #
+ if channel and method_sig in self.Channel._IMMEDIATE_METHODS:
+ self.channels[channel].dispatch_method(
+ method_sig, args, content,
+ )
+ continue
+
+ #
+ # Not the channel and/or method we were looking for. Queue
+ # this method for later
+ #
+ self.channels[channel].method_queue.append(
+ (method_sig, args, content),
+ )
+
+ #
+ # If we just queued up a method for channel 0 (the Connection
+ # itself) it's probably a close method in reaction to some
+ # error, so deal with it right away.
+ #
+ if not channel:
+ self.wait()
+
+ def channel(self, channel_id=None):
+ """Fetch a Channel object identified by the numeric channel_id, or
+ create that object if it doesn't already exist."""
+ try:
+ return self.channels[channel_id]
+ except KeyError:
+ return self.Channel(self, channel_id)
+
+ def is_alive(self):
+ if HAS_MSG_PEEK:
+ sock = self.sock
+ prev = sock.gettimeout()
+ sock.settimeout(0.0001)
+ try:
+ sock.recv(1, socket.MSG_PEEK)
+ except socket.timeout:
+ pass
+ except socket.error:
+ return False
+ finally:
+ sock.settimeout(prev)
+ return True
+
+ def drain_events(self, timeout=None):
+ """Wait for an event on a channel."""
+ chanmap = self.channels
+ chanid, method_sig, args, content = self._wait_multiple(
+ chanmap, None, timeout=timeout,
+ )
+
+ channel = chanmap[chanid]
+
+ if (content and
+ channel.auto_decode and
+ hasattr(content, 'content_encoding')):
+ try:
+ content.body = content.body.decode(content.content_encoding)
+ except Exception:
+ pass
+
+ amqp_method = (self._method_override.get(method_sig) or
+ channel._METHOD_MAP.get(method_sig, None))
+
+ if amqp_method is None:
+ raise AMQPNotImplementedError(
+ 'Unknown AMQP method {0!r}'.format(method_sig))
+
+ if content is None:
+ return amqp_method(channel, args)
+ else:
+ return amqp_method(channel, args, content)
+
+ def read_timeout(self, timeout=None):
+ if timeout is None:
+ return self.method_reader.read_method()
+ sock = self.sock
+ prev = sock.gettimeout()
+ if prev != timeout:
+ sock.settimeout(timeout)
+ try:
+ try:
+ return self.method_reader.read_method()
+ except SSLError as exc:
+ # http://bugs.python.org/issue10272
+ if 'timed out' in str(exc):
+ raise socket.timeout()
+ # Non-blocking SSL sockets can throw SSLError
+ if 'The operation did not complete' in str(exc):
+ raise socket.timeout()
+ raise
+ finally:
+ if prev != timeout:
+ sock.settimeout(prev)
+
+ def _wait_multiple(self, channels, allowed_methods, timeout=None):
+ for channel_id, channel in items(channels):
+ method_queue = channel.method_queue
+ for queued_method in method_queue:
+ method_sig = queued_method[0]
+ if (allowed_methods is None or
+ method_sig in allowed_methods or
+ method_sig == (20, 40)):
+ method_queue.remove(queued_method)
+ method_sig, args, content = queued_method
+ return channel_id, method_sig, args, content
+
+ # Nothing queued, need to wait for a method from the peer
+ read_timeout = self.read_timeout
+ wait = self.wait
+ while 1:
+ channel, method_sig, args, content = read_timeout(timeout)
+
+ if channel in channels and (
+ allowed_methods is None or
+ method_sig in allowed_methods or
+ method_sig == (20, 40)):
+ return channel, method_sig, args, content
+
+ # Not the channel and/or method we were looking for. Queue
+ # this method for later
+ channels[channel].method_queue.append((method_sig, args, content))
+
+ #
+ # If we just queued up a method for channel 0 (the Connection
+ # itself) it's probably a close method in reaction to some
+ # error, so deal with it right away.
+ #
+ if channel == 0:
+ wait()
+
+ def _dispatch_basic_return(self, channel, args, msg):
+ reply_code = args.read_short()
+ reply_text = args.read_shortstr()
+ exchange = args.read_shortstr()
+ routing_key = args.read_shortstr()
+
+ exc = error_for_code(reply_code, reply_text, (50, 60), ChannelError)
+ handlers = channel.events.get('basic_return')
+ if not handlers:
+ raise exc
+ for callback in handlers:
+ callback(exc, exchange, routing_key, msg)
+
+ def close(self, reply_code=0, reply_text='', method_sig=(0, 0)):
+ """Request a connection close
+
+ This method indicates that the sender wants to close the
+ connection. This may be due to internal conditions (e.g. a
+ forced shut-down) or due to an error handling a specific
+ method, i.e. an exception. When a close is due to an
+ exception, the sender provides the class and method id of the
+ method which caused the exception.
+
+ RULE:
+
+ After sending this method any received method except the
+ Close-OK method MUST be discarded.
+
+ RULE:
+
+ The peer sending this method MAY use a counter or timeout
+ to detect failure of the other peer to respond correctly
+ with the Close-OK method.
+
+ RULE:
+
+ When a server receives the Close method from a client it
+ MUST delete all server-side resources associated with the
+ client's context. A client CANNOT reconnect to a context
+ after sending or receiving a Close method.
+
+ PARAMETERS:
+ reply_code: short
+
+ The reply code. The AMQ reply codes are defined in AMQ
+ RFC 011.
+
+ reply_text: shortstr
+
+ The localised reply text. This text can be logged as an
+ aid to resolving issues.
+
+ class_id: short
+
+ failing method class
+
+ When the close is provoked by a method exception, this
+ is the class of the method.
+
+ method_id: short
+
+ failing method ID
+
+ When the close is provoked by a method exception, this
+ is the ID of the method.
+
+ """
+ if self.transport is None:
+ # already closed
+ return
+
+ args = AMQPWriter()
+ args.write_short(reply_code)
+ args.write_shortstr(reply_text)
+ args.write_short(method_sig[0]) # class_id
+ args.write_short(method_sig[1]) # method_id
+ self._send_method((10, 50), args)
+ return self.wait(allowed_methods=[
+ (10, 50), # Connection.close
+ (10, 51), # Connection.close_ok
+ ])
+
+ def _close(self, args):
+ """Request a connection close
+
+ This method indicates that the sender wants to close the
+ connection. This may be due to internal conditions (e.g. a
+ forced shut-down) or due to an error handling a specific
+ method, i.e. an exception. When a close is due to an
+ exception, the sender provides the class and method id of the
+ method which caused the exception.
+
+ RULE:
+
+ After sending this method any received method except the
+ Close-OK method MUST be discarded.
+
+ RULE:
+
+ The peer sending this method MAY use a counter or timeout
+ to detect failure of the other peer to respond correctly
+ with the Close-OK method.
+
+ RULE:
+
+ When a server receives the Close method from a client it
+ MUST delete all server-side resources associated with the
+ client's context. A client CANNOT reconnect to a context
+ after sending or receiving a Close method.
+
+ PARAMETERS:
+ reply_code: short
+
+ The reply code. The AMQ reply codes are defined in AMQ
+ RFC 011.
+
+ reply_text: shortstr
+
+ The localised reply text. This text can be logged as an
+ aid to resolving issues.
+
+ class_id: short
+
+ failing method class
+
+ When the close is provoked by a method exception, this
+ is the class of the method.
+
+ method_id: short
+
+ failing method ID
+
+ When the close is provoked by a method exception, this
+ is the ID of the method.
+
+ """
+ reply_code = args.read_short()
+ reply_text = args.read_shortstr()
+ class_id = args.read_short()
+ method_id = args.read_short()
+
+ self._x_close_ok()
+
+ raise error_for_code(reply_code, reply_text,
+ (class_id, method_id), ConnectionError)
+
+ def _blocked(self, args):
+ """RabbitMQ Extension."""
+ reason = args.read_shortstr()
+ if self.on_blocked:
+ return self.on_blocked(reason)
+
+ def _unblocked(self, *args):
+ if self.on_unblocked:
+ return self.on_unblocked()
+
+ def _x_close_ok(self):
+ """Confirm a connection close
+
+ This method confirms a Connection.Close method and tells the
+ recipient that it is safe to release resources for the
+ connection and close the socket.
+
+ RULE:
+
+ A peer that detects a socket closure without having
+ received a Close-Ok handshake method SHOULD log the error.
+
+ """
+ self._send_method((10, 51))
+ self._do_close()
+
+ def _close_ok(self, args):
+ """Confirm a connection close
+
+ This method confirms a Connection.Close method and tells the
+ recipient that it is safe to release resources for the
+ connection and close the socket.
+
+ RULE:
+
+ A peer that detects a socket closure without having
+ received a Close-Ok handshake method SHOULD log the error.
+
+ """
+ self._do_close()
+
+ def _x_open(self, virtual_host, capabilities=''):
+ """Open connection to virtual host
+
+ This method opens a connection to a virtual host, which is a
+ collection of resources, and acts to separate multiple
+ application domains within a server.
+
+ RULE:
+
+ The client MUST open the context before doing any work on
+ the connection.
+
+ PARAMETERS:
+ virtual_host: shortstr
+
+ virtual host name
+
+ The name of the virtual host to work with.
+
+ RULE:
+
+ If the server supports multiple virtual hosts, it
+ MUST enforce a full separation of exchanges,
+ queues, and all associated entities per virtual
+ host. An application, connected to a specific
+ virtual host, MUST NOT be able to access resources
+ of another virtual host.
+
+ RULE:
+
+ The server SHOULD verify that the client has
+ permission to access the specified virtual host.
+
+ RULE:
+
+ The server MAY configure arbitrary limits per
+ virtual host, such as the number of each type of
+ entity that may be used, per connection and/or in
+ total.
+
+ capabilities: shortstr
+
+ required capabilities
+
+ The client may specify a number of capability names,
+ delimited by spaces. The server can use this string
+ to how to process the client's connection request.
+
+ """
+ args = AMQPWriter()
+ args.write_shortstr(virtual_host)
+ args.write_shortstr(capabilities)
+ args.write_bit(False)
+ self._send_method((10, 40), args)
+ return self.wait(allowed_methods=[
+ (10, 41), # Connection.open_ok
+ ])
+
+ def _open_ok(self, args):
+ """Signal that the connection is ready
+
+ This method signals to the client that the connection is ready
+ for use.
+
+ PARAMETERS:
+ known_hosts: shortstr (deprecated)
+
+ """
+ AMQP_LOGGER.debug('Open OK!')
+
+ def _secure(self, args):
+ """Security mechanism challenge
+
+ The SASL protocol works by exchanging challenges and responses
+ until both peers have received sufficient information to
+ authenticate each other. This method challenges the client to
+ provide more information.
+
+ PARAMETERS:
+ challenge: longstr
+
+ security challenge data
+
+ Challenge information, a block of opaque binary data
+ passed to the security mechanism.
+
+ """
+ challenge = args.read_longstr() # noqa
+
+ def _x_secure_ok(self, response):
+ """Security mechanism response
+
+ This method attempts to authenticate, passing a block of SASL
+ data for the security mechanism at the server side.
+
+ PARAMETERS:
+ response: longstr
+
+ security response data
+
+ A block of opaque data passed to the security
+ mechanism. The contents of this data are defined by
+ the SASL security mechanism.
+
+ """
+ args = AMQPWriter()
+ args.write_longstr(response)
+ self._send_method((10, 21), args)
+
+ def _start(self, args):
+ """Start connection negotiation
+
+ This method starts the connection negotiation process by
+ telling the client the protocol version that the server
+ proposes, along with a list of security mechanisms which the
+ client can use for authentication.
+
+ RULE:
+
+ If the client cannot handle the protocol version suggested
+ by the server it MUST close the socket connection.
+
+ RULE:
+
+ The server MUST provide a protocol version that is lower
+ than or equal to that requested by the client in the
+ protocol header. If the server cannot support the
+ specified protocol it MUST NOT send this method, but MUST
+ close the socket connection.
+
+ PARAMETERS:
+ version_major: octet
+
+ protocol major version
+
+ The protocol major version that the server agrees to
+ use, which cannot be higher than the client's major
+ version.
+
+ version_minor: octet
+
+ protocol major version
+
+ The protocol minor version that the server agrees to
+ use, which cannot be higher than the client's minor
+ version.
+
+ server_properties: table
+
+ server properties
+
+ mechanisms: longstr
+
+ available security mechanisms
+
+ A list of the security mechanisms that the server
+ supports, delimited by spaces. Currently ASL supports
+ these mechanisms: PLAIN.
+
+ locales: longstr
+
+ available message locales
+
+ A list of the message locales that the server
+ supports, delimited by spaces. The locale defines the
+ language in which the server will send reply texts.
+
+ RULE:
+
+ All servers MUST support at least the en_US
+ locale.
+
+ """
+ self.version_major = args.read_octet()
+ self.version_minor = args.read_octet()
+ self.server_properties = args.read_table()
+ self.mechanisms = args.read_longstr().split(' ')
+ self.locales = args.read_longstr().split(' ')
+
+ AMQP_LOGGER.debug(
+ START_DEBUG_FMT,
+ self.version_major, self.version_minor,
+ self.server_properties, self.mechanisms, self.locales,
+ )
+
+ def _x_start_ok(self, client_properties, mechanism, response, locale):
+ """Select security mechanism and locale
+
+ This method selects a SASL security mechanism. ASL uses SASL
+ (RFC2222) to negotiate authentication and encryption.
+
+ PARAMETERS:
+ client_properties: table
+
+ client properties
+
+ mechanism: shortstr
+
+ selected security mechanism
+
+ A single security mechanisms selected by the client,
+ which must be one of those specified by the server.
+
+ RULE:
+
+ The client SHOULD authenticate using the highest-
+ level security profile it can handle from the list
+ provided by the server.
+
+ RULE:
+
+ The mechanism field MUST contain one of the
+ security mechanisms proposed by the server in the
+ Start method. If it doesn't, the server MUST close
+ the socket.
+
+ response: longstr
+
+ security response data
+
+ A block of opaque data passed to the security
+ mechanism. The contents of this data are defined by
+ the SASL security mechanism. For the PLAIN security
+ mechanism this is defined as a field table holding two
+ fields, LOGIN and PASSWORD.
+
+ locale: shortstr
+
+ selected message locale
+
+ A single message local selected by the client, which
+ must be one of those specified by the server.
+
+ """
+ if self.server_capabilities.get('consumer_cancel_notify'):
+ if 'capabilities' not in client_properties:
+ client_properties['capabilities'] = {}
+ client_properties['capabilities']['consumer_cancel_notify'] = True
+ if self.server_capabilities.get('connection.blocked'):
+ if 'capabilities' not in client_properties:
+ client_properties['capabilities'] = {}
+ client_properties['capabilities']['connection.blocked'] = True
+ args = AMQPWriter()
+ args.write_table(client_properties)
+ args.write_shortstr(mechanism)
+ args.write_longstr(response)
+ args.write_shortstr(locale)
+ self._send_method((10, 11), args)
+
+ def _tune(self, args):
+ """Propose connection tuning parameters
+
+ This method proposes a set of connection configuration values
+ to the client. The client can accept and/or adjust these.
+
+ PARAMETERS:
+ channel_max: short
+
+ proposed maximum channels
+
+ The maximum total number of channels that the server
+ allows per connection. Zero means that the server does
+ not impose a fixed limit, but the number of allowed
+ channels may be limited by available server resources.
+
+ frame_max: long
+
+ proposed maximum frame size
+
+ The largest frame size that the server proposes for
+ the connection. The client can negotiate a lower
+ value. Zero means that the server does not impose any
+ specific limit but may reject very large frames if it
+ cannot allocate resources for them.
+
+ RULE:
+
+ Until the frame-max has been negotiated, both
+ peers MUST accept frames of up to 4096 octets
+ large. The minimum non-zero value for the frame-
+ max field is 4096.
+
+ heartbeat: short
+
+ desired heartbeat delay
+
+ The delay, in seconds, of the connection heartbeat
+ that the server wants. Zero means the server does not
+ want a heartbeat.
+
+ """
+ client_heartbeat = self.client_heartbeat or 0
+ self.channel_max = args.read_short() or self.channel_max
+ self.frame_max = args.read_long() or self.frame_max
+ self.method_writer.frame_max = self.frame_max
+ self.server_heartbeat = args.read_short() or 0
+
+ # negotiate the heartbeat interval to the smaller of the
+ # specified values
+ if self.server_heartbeat == 0 or client_heartbeat == 0:
+ self.heartbeat = max(self.server_heartbeat, client_heartbeat)
+ else:
+ self.heartbeat = min(self.server_heartbeat, client_heartbeat)
+
+ # Ignore server heartbeat if client_heartbeat is disabled
+ if not self.client_heartbeat:
+ self.heartbeat = 0
+
+ self._x_tune_ok(self.channel_max, self.frame_max, self.heartbeat)
+
+ def send_heartbeat(self):
+ self.transport.write_frame(8, 0, bytes())
+
+ def heartbeat_tick(self, rate=2):
+ """Send heartbeat packets, if necessary, and fail if none have been
+ received recently. This should be called frequently, on the order of
+ once per second.
+
+ :keyword rate: Ignored
+ """
+ if not self.heartbeat:
+ return
+
+ # treat actual data exchange in either direction as a heartbeat
+ sent_now = self.method_writer.bytes_sent
+ recv_now = self.method_reader.bytes_recv
+ if self.prev_sent is None or self.prev_sent != sent_now:
+ self.last_heartbeat_sent = monotonic()
+ if self.prev_recv is None or self.prev_recv != recv_now:
+ self.last_heartbeat_received = monotonic()
+ self.prev_sent, self.prev_recv = sent_now, recv_now
+
+ # send a heartbeat if it's time to do so
+ if monotonic() > self.last_heartbeat_sent + self.heartbeat:
+ self.send_heartbeat()
+ self.last_heartbeat_sent = monotonic()
+
+ # if we've missed two intervals' heartbeats, fail; this gives the
+ # server enough time to send heartbeats a little late
+ if (self.last_heartbeat_received and
+ self.last_heartbeat_received + 2 *
+ self.heartbeat < monotonic()):
+ raise ConnectionForced('Too many heartbeats missed')
+
+ def _x_tune_ok(self, channel_max, frame_max, heartbeat):
+ """Negotiate connection tuning parameters
+
+ This method sends the client's connection tuning parameters to
+ the server. Certain fields are negotiated, others provide
+ capability information.
+
+ PARAMETERS:
+ channel_max: short
+
+ negotiated maximum channels
+
+ The maximum total number of channels that the client
+ will use per connection. May not be higher than the
+ value specified by the server.
+
+ RULE:
+
+ The server MAY ignore the channel-max value or MAY
+ use it for tuning its resource allocation.
+
+ frame_max: long
+
+ negotiated maximum frame size
+
+ The largest frame size that the client and server will
+ use for the connection. Zero means that the client
+ does not impose any specific limit but may reject very
+ large frames if it cannot allocate resources for them.
+ Note that the frame-max limit applies principally to
+ content frames, where large contents can be broken
+ into frames of arbitrary size.
+
+ RULE:
+
+ Until the frame-max has been negotiated, both
+ peers must accept frames of up to 4096 octets
+ large. The minimum non-zero value for the frame-
+ max field is 4096.
+
+ heartbeat: short
+
+ desired heartbeat delay
+
+ The delay, in seconds, of the connection heartbeat
+ that the client wants. Zero means the client does not
+ want a heartbeat.
+
+ """
+ args = AMQPWriter()
+ args.write_short(channel_max)
+ args.write_long(frame_max)
+ args.write_short(heartbeat or 0)
+ self._send_method((10, 31), args)
+ self._wait_tune_ok = False
+
+ @property
+ def sock(self):
+ return self.transport.sock
+
+ @property
+ def server_capabilities(self):
+ return self.server_properties.get('capabilities') or {}
+
+ _METHOD_MAP = {
+ (10, 10): _start,
+ (10, 20): _secure,
+ (10, 30): _tune,
+ (10, 41): _open_ok,
+ (10, 50): _close,
+ (10, 51): _close_ok,
+ (10, 60): _blocked,
+ (10, 61): _unblocked,
+ }
+
+ _IMMEDIATE_METHODS = []
+ connection_errors = (
+ ConnectionError,
+ socket.error,
+ IOError,
+ OSError,
+ )
+ channel_errors = (ChannelError, )
+ recoverable_connection_errors = (
+ RecoverableConnectionError,
+ socket.error,
+ IOError,
+ OSError,
+ )
+ recoverable_channel_errors = (
+ RecoverableChannelError,
+ )
diff --git a/amqp/exceptions.py b/amqp/exceptions.py
new file mode 100644
index 0000000..e3e144a
--- /dev/null
+++ b/amqp/exceptions.py
@@ -0,0 +1,258 @@
+"""Exceptions used by amqp"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+from __future__ import absolute_import
+
+from struct import pack, unpack
+
+__all__ = [
+ 'AMQPError',
+ 'ConnectionError', 'ChannelError',
+ 'RecoverableConnectionError', 'IrrecoverableConnectionError',
+ 'RecoverableChannelError', 'IrrecoverableChannelError',
+ 'ConsumerCancelled', 'ContentTooLarge', 'NoConsumers',
+ 'ConnectionForced', 'InvalidPath', 'AccessRefused', 'NotFound',
+ 'ResourceLocked', 'PreconditionFailed', 'FrameError', 'FrameSyntaxError',
+ 'InvalidCommand', 'ChannelNotOpen', 'UnexpectedFrame', 'ResourceError',
+ 'NotAllowed', 'AMQPNotImplementedError', 'InternalError',
+]
+
+
+class AMQPError(Exception):
+ code = 0
+
+ def __init__(self, reply_text=None, method_sig=None,
+ method_name=None, reply_code=None):
+ self.message = reply_text
+ self.reply_code = reply_code or self.code
+ self.reply_text = reply_text
+ self.method_sig = method_sig
+ self.method_name = method_name or ''
+ if method_sig and not self.method_name:
+ self.method_name = METHOD_NAME_MAP.get(method_sig, '')
+ Exception.__init__(self, reply_code,
+ reply_text, method_sig, self.method_name)
+
+ def __str__(self):
+ if self.method:
+ return '{0.method}: ({0.reply_code}) {0.reply_text}'.format(self)
+ return self.reply_text or '<AMQPError: unknown error>'
+
+ @property
+ def method(self):
+ return self.method_name or self.method_sig
+
+
+class ConnectionError(AMQPError):
+ pass
+
+
+class ChannelError(AMQPError):
+ pass
+
+
+class RecoverableChannelError(ChannelError):
+ pass
+
+
+class IrrecoverableChannelError(ChannelError):
+ pass
+
+
+class RecoverableConnectionError(ConnectionError):
+ pass
+
+
+class IrrecoverableConnectionError(ConnectionError):
+ pass
+
+
+class Blocked(RecoverableConnectionError):
+ pass
+
+
+class ConsumerCancelled(RecoverableConnectionError):
+ pass
+
+
+class ContentTooLarge(RecoverableChannelError):
+ code = 311
+
+
+class NoConsumers(RecoverableChannelError):
+ code = 313
+
+
+class ConnectionForced(RecoverableConnectionError):
+ code = 320
+
+
+class InvalidPath(IrrecoverableConnectionError):
+ code = 402
+
+
+class AccessRefused(IrrecoverableChannelError):
+ code = 403
+
+
+class NotFound(IrrecoverableChannelError):
+ code = 404
+
+
+class ResourceLocked(RecoverableChannelError):
+ code = 405
+
+
+class PreconditionFailed(IrrecoverableChannelError):
+ code = 406
+
+
+class FrameError(IrrecoverableConnectionError):
+ code = 501
+
+
+class FrameSyntaxError(IrrecoverableConnectionError):
+ code = 502
+
+
+class InvalidCommand(IrrecoverableConnectionError):
+ code = 503
+
+
+class ChannelNotOpen(IrrecoverableConnectionError):
+ code = 504
+
+
+class UnexpectedFrame(IrrecoverableConnectionError):
+ code = 505
+
+
+class ResourceError(RecoverableConnectionError):
+ code = 506
+
+
+class NotAllowed(IrrecoverableConnectionError):
+ code = 530
+
+
+class AMQPNotImplementedError(IrrecoverableConnectionError):
+ code = 540
+
+
+class InternalError(IrrecoverableConnectionError):
+ code = 541
+
+
+ERROR_MAP = {
+ 311: ContentTooLarge,
+ 313: NoConsumers,
+ 320: ConnectionForced,
+ 402: InvalidPath,
+ 403: AccessRefused,
+ 404: NotFound,
+ 405: ResourceLocked,
+ 406: PreconditionFailed,
+ 501: FrameError,
+ 502: FrameSyntaxError,
+ 503: InvalidCommand,
+ 504: ChannelNotOpen,
+ 505: UnexpectedFrame,
+ 506: ResourceError,
+ 530: NotAllowed,
+ 540: AMQPNotImplementedError,
+ 541: InternalError,
+}
+
+
+def error_for_code(code, text, method, default):
+ try:
+ return ERROR_MAP[code](text, method, reply_code=code)
+ except KeyError:
+ return default(text, method, reply_code=code)
+
+
+def raise_for_code(code, text, method, default):
+ raise error_for_code(code, text, method, default)
+
+
+METHOD_NAME_MAP = {
+ (10, 10): 'Connection.start',
+ (10, 11): 'Connection.start_ok',
+ (10, 20): 'Connection.secure',
+ (10, 21): 'Connection.secure_ok',
+ (10, 30): 'Connection.tune',
+ (10, 31): 'Connection.tune_ok',
+ (10, 40): 'Connection.open',
+ (10, 41): 'Connection.open_ok',
+ (10, 50): 'Connection.close',
+ (10, 51): 'Connection.close_ok',
+ (20, 10): 'Channel.open',
+ (20, 11): 'Channel.open_ok',
+ (20, 20): 'Channel.flow',
+ (20, 21): 'Channel.flow_ok',
+ (20, 40): 'Channel.close',
+ (20, 41): 'Channel.close_ok',
+ (30, 10): 'Access.request',
+ (30, 11): 'Access.request_ok',
+ (40, 10): 'Exchange.declare',
+ (40, 11): 'Exchange.declare_ok',
+ (40, 20): 'Exchange.delete',
+ (40, 21): 'Exchange.delete_ok',
+ (40, 30): 'Exchange.bind',
+ (40, 31): 'Exchange.bind_ok',
+ (40, 40): 'Exchange.unbind',
+ (40, 41): 'Exchange.unbind_ok',
+ (50, 10): 'Queue.declare',
+ (50, 11): 'Queue.declare_ok',
+ (50, 20): 'Queue.bind',
+ (50, 21): 'Queue.bind_ok',
+ (50, 30): 'Queue.purge',
+ (50, 31): 'Queue.purge_ok',
+ (50, 40): 'Queue.delete',
+ (50, 41): 'Queue.delete_ok',
+ (50, 50): 'Queue.unbind',
+ (50, 51): 'Queue.unbind_ok',
+ (60, 10): 'Basic.qos',
+ (60, 11): 'Basic.qos_ok',
+ (60, 20): 'Basic.consume',
+ (60, 21): 'Basic.consume_ok',
+ (60, 30): 'Basic.cancel',
+ (60, 31): 'Basic.cancel_ok',
+ (60, 40): 'Basic.publish',
+ (60, 50): 'Basic.return',
+ (60, 60): 'Basic.deliver',
+ (60, 70): 'Basic.get',
+ (60, 71): 'Basic.get_ok',
+ (60, 72): 'Basic.get_empty',
+ (60, 80): 'Basic.ack',
+ (60, 90): 'Basic.reject',
+ (60, 100): 'Basic.recover_async',
+ (60, 110): 'Basic.recover',
+ (60, 111): 'Basic.recover_ok',
+ (60, 120): 'Basic.nack',
+ (90, 10): 'Tx.select',
+ (90, 11): 'Tx.select_ok',
+ (90, 20): 'Tx.commit',
+ (90, 21): 'Tx.commit_ok',
+ (90, 30): 'Tx.rollback',
+ (90, 31): 'Tx.rollback_ok',
+ (85, 10): 'Confirm.select',
+ (85, 11): 'Confirm.select_ok',
+}
+
+
+for _method_id, _method_name in list(METHOD_NAME_MAP.items()):
+ METHOD_NAME_MAP[unpack('>I', pack('>HH', *_method_id))[0]] = _method_name
diff --git a/amqp/five.py b/amqp/five.py
new file mode 100644
index 0000000..5157df5
--- /dev/null
+++ b/amqp/five.py
@@ -0,0 +1,188 @@
+# -*- coding: utf-8 -*-
+"""
+ celery.five
+ ~~~~~~~~~~~
+
+ Compatibility implementations of features
+ only available in newer Python versions.
+
+
+"""
+from __future__ import absolute_import
+
+############## py3k #########################################################
+import sys
+PY3 = sys.version_info[0] == 3
+
+try:
+ reload = reload # noqa
+except NameError: # pragma: no cover
+ from imp import reload # noqa
+
+try:
+ from UserList import UserList # noqa
+except ImportError: # pragma: no cover
+ from collections import UserList # noqa
+
+try:
+ from UserDict import UserDict # noqa
+except ImportError: # pragma: no cover
+ from collections import UserDict # noqa
+
+
+if PY3:
+ import builtins
+
+ from queue import Queue, Empty
+ from itertools import zip_longest
+ from io import StringIO, BytesIO
+
+ map = map
+ string = str
+ string_t = str
+ long_t = int
+ text_t = str
+ range = range
+ int_types = (int, )
+
+ open_fqdn = 'builtins.open'
+
+ def items(d):
+ return d.items()
+
+ def keys(d):
+ return d.keys()
+
+ def values(d):
+ return d.values()
+
+ def nextfun(it):
+ return it.__next__
+
+ exec_ = getattr(builtins, 'exec')
+
+ def reraise(tp, value, tb=None):
+ if value.__traceback__ is not tb:
+ raise value.with_traceback(tb)
+ raise value
+
+ class WhateverIO(StringIO):
+
+ def write(self, data):
+ if isinstance(data, bytes):
+ data = data.encode()
+ StringIO.write(self, data)
+
+else:
+ import __builtin__ as builtins # noqa
+ from Queue import Queue, Empty # noqa
+ from itertools import imap as map, izip_longest as zip_longest # noqa
+ from StringIO import StringIO # noqa
+ string = unicode # noqa
+ string_t = basestring # noqa
+ text_t = unicode
+ long_t = long # noqa
+ range = xrange
+ int_types = (int, long)
+
+ open_fqdn = '__builtin__.open'
+
+ def items(d): # noqa
+ return d.iteritems()
+
+ def keys(d): # noqa
+ return d.iterkeys()
+
+ def values(d): # noqa
+ return d.itervalues()
+
+ def nextfun(it): # noqa
+ return it.next
+
+ def exec_(code, globs=None, locs=None):
+ """Execute code in a namespace."""
+ if globs is None:
+ frame = sys._getframe(1)
+ globs = frame.f_globals
+ if locs is None:
+ locs = frame.f_locals
+ del frame
+ elif locs is None:
+ locs = globs
+ exec("""exec code in globs, locs""")
+
+ exec_("""def reraise(tp, value, tb=None): raise tp, value, tb""")
+
+ BytesIO = WhateverIO = StringIO # noqa
+
+
+def with_metaclass(Type, skip_attrs=set(['__dict__', '__weakref__'])):
+ """Class decorator to set metaclass.
+
+ Works with both Python 3 and Python 3 and it does not add
+ an extra class in the lookup order like ``six.with_metaclass`` does
+ (that is -- it copies the original class instead of using inheritance).
+
+ """
+
+ def _clone_with_metaclass(Class):
+ attrs = dict((key, value) for key, value in items(vars(Class))
+ if key not in skip_attrs)
+ return Type(Class.__name__, Class.__bases__, attrs)
+
+ return _clone_with_metaclass
+
+############## time.monotonic ################################################
+
+if sys.version_info < (3, 3):
+
+ import platform
+ SYSTEM = platform.system()
+
+ if SYSTEM == 'Darwin':
+ import ctypes
+ from ctypes.util import find_library
+ libSystem = ctypes.CDLL('libSystem.dylib')
+ CoreServices = ctypes.CDLL(find_library('CoreServices'),
+ use_errno=True)
+ mach_absolute_time = libSystem.mach_absolute_time
+ mach_absolute_time.restype = ctypes.c_uint64
+ absolute_to_nanoseconds = CoreServices.AbsoluteToNanoseconds
+ absolute_to_nanoseconds.restype = ctypes.c_uint64
+ absolute_to_nanoseconds.argtypes = [ctypes.c_uint64]
+
+ def _monotonic():
+ return absolute_to_nanoseconds(mach_absolute_time()) * 1e-9
+
+ elif SYSTEM == 'Linux':
+ # from stackoverflow:
+ # questions/1205722/how-do-i-get-monotonic-time-durations-in-python
+ import ctypes
+ import os
+
+ CLOCK_MONOTONIC = 1 # see <linux/time.h>
+
+ class timespec(ctypes.Structure):
+ _fields_ = [
+ ('tv_sec', ctypes.c_long),
+ ('tv_nsec', ctypes.c_long),
+ ]
+
+ librt = ctypes.CDLL('librt.so.1', use_errno=True)
+ clock_gettime = librt.clock_gettime
+ clock_gettime.argtypes = [
+ ctypes.c_int, ctypes.POINTER(timespec),
+ ]
+
+ def _monotonic(): # noqa
+ t = timespec()
+ if clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(t)) != 0:
+ errno_ = ctypes.get_errno()
+ raise OSError(errno_, os.strerror(errno_))
+ return t.tv_sec + t.tv_nsec * 1e-9
+ else:
+ from time import time as _monotonic
+try:
+ from time import monotonic
+except ImportError:
+ monotonic = _monotonic # noqa
diff --git a/amqp/method_framing.py b/amqp/method_framing.py
new file mode 100644
index 0000000..b454524
--- /dev/null
+++ b/amqp/method_framing.py
@@ -0,0 +1,231 @@
+"""Convert between frames and higher-level AMQP methods"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+from __future__ import absolute_import
+
+from collections import defaultdict, deque
+from struct import pack, unpack
+
+from .basic_message import Message
+from .exceptions import AMQPError, UnexpectedFrame
+from .five import range, string
+from .serialization import AMQPReader
+
+__all__ = ['MethodReader']
+
+#
+# MethodReader needs to know which methods are supposed
+# to be followed by content headers and bodies.
+#
+_CONTENT_METHODS = [
+ (60, 50), # Basic.return
+ (60, 60), # Basic.deliver
+ (60, 71), # Basic.get_ok
+]
+
+
+class _PartialMessage(object):
+ """Helper class to build up a multi-frame method."""
+
+ def __init__(self, method_sig, args, channel):
+ self.method_sig = method_sig
+ self.args = args
+ self.msg = Message()
+ self.body_parts = []
+ self.body_received = 0
+ self.body_size = None
+ self.complete = False
+
+ def add_header(self, payload):
+ class_id, weight, self.body_size = unpack('>HHQ', payload[:12])
+ self.msg._load_properties(payload[12:])
+ self.complete = (self.body_size == 0)
+
+ def add_payload(self, payload):
+ parts = self.body_parts
+ self.body_received += len(payload)
+ if self.body_received == self.body_size:
+ if parts:
+ parts.append(payload)
+ self.msg.body = bytes().join(parts)
+ else:
+ self.msg.body = payload
+ self.complete = True
+ else:
+ parts.append(payload)
+
+
+class MethodReader(object):
+ """Helper class to receive frames from the broker, combine them if
+ necessary with content-headers and content-bodies into complete methods.
+
+ Normally a method is represented as a tuple containing
+ (channel, method_sig, args, content).
+
+ In the case of a framing error, an :exc:`ConnectionError` is placed
+ in the queue.
+
+ In the case of unexpected frames, a tuple made up of
+ ``(channel, ChannelError)`` is placed in the queue.
+
+ """
+
+ def __init__(self, source):
+ self.source = source
+ self.queue = deque()
+ self.running = False
+ self.partial_messages = {}
+ self.heartbeats = 0
+ # For each channel, which type is expected next
+ self.expected_types = defaultdict(lambda: 1)
+ # not an actual byte count, just incremented whenever we receive
+ self.bytes_recv = 0
+ self._quick_put = self.queue.append
+ self._quick_get = self.queue.popleft
+
+ def _next_method(self):
+ """Read the next method from the source, once one complete method has
+ been assembled it is placed in the internal queue."""
+ queue = self.queue
+ put = self._quick_put
+ read_frame = self.source.read_frame
+ while not queue:
+ try:
+ frame_type, channel, payload = read_frame()
+ except Exception as exc:
+ #
+ # Connection was closed? Framing Error?
+ #
+ put(exc)
+ break
+
+ self.bytes_recv += 1
+
+ if frame_type not in (self.expected_types[channel], 8):
+ put((
+ channel,
+ UnexpectedFrame(
+ 'Received frame {0} while expecting type: {1}'.format(
+ frame_type, self.expected_types[channel]))))
+ elif frame_type == 1:
+ self._process_method_frame(channel, payload)
+ elif frame_type == 2:
+ self._process_content_header(channel, payload)
+ elif frame_type == 3:
+ self._process_content_body(channel, payload)
+ elif frame_type == 8:
+ self._process_heartbeat(channel, payload)
+
+ def _process_heartbeat(self, channel, payload):
+ self.heartbeats += 1
+
+ def _process_method_frame(self, channel, payload):
+ """Process Method frames"""
+ method_sig = unpack('>HH', payload[:4])
+ args = AMQPReader(payload[4:])
+
+ if method_sig in _CONTENT_METHODS:
+ #
+ # Save what we've got so far and wait for the content-header
+ #
+ self.partial_messages[channel] = _PartialMessage(
+ method_sig, args, channel,
+ )
+ self.expected_types[channel] = 2
+ else:
+ self._quick_put((channel, method_sig, args, None))
+
+ def _process_content_header(self, channel, payload):
+ """Process Content Header frames"""
+ partial = self.partial_messages[channel]
+ partial.add_header(payload)
+
+ if partial.complete:
+ #
+ # a bodyless message, we're done
+ #
+ self._quick_put((channel, partial.method_sig,
+ partial.args, partial.msg))
+ self.partial_messages.pop(channel, None)
+ self.expected_types[channel] = 1
+ else:
+ #
+ # wait for the content-body
+ #
+ self.expected_types[channel] = 3
+
+ def _process_content_body(self, channel, payload):
+ """Process Content Body frames"""
+ partial = self.partial_messages[channel]
+ partial.add_payload(payload)
+ if partial.complete:
+ #
+ # Stick the message in the queue and go back to
+ # waiting for method frames
+ #
+ self._quick_put((channel, partial.method_sig,
+ partial.args, partial.msg))
+ self.partial_messages.pop(channel, None)
+ self.expected_types[channel] = 1
+
+ def read_method(self):
+ """Read a method from the peer."""
+ self._next_method()
+ m = self._quick_get()
+ if isinstance(m, Exception):
+ raise m
+ if isinstance(m, tuple) and isinstance(m[1], AMQPError):
+ raise m[1]
+ return m
+
+
+class MethodWriter(object):
+ """Convert AMQP methods into AMQP frames and send them out
+ to the peer."""
+
+ def __init__(self, dest, frame_max):
+ self.dest = dest
+ self.frame_max = frame_max
+ self.bytes_sent = 0
+
+ def write_method(self, channel, method_sig, args, content=None):
+ write_frame = self.dest.write_frame
+ payload = pack('>HH', method_sig[0], method_sig[1]) + args
+
+ if content:
+ # do this early, so we can raise an exception if there's a
+ # problem with the content properties, before sending the
+ # first frame
+ body = content.body
+ if isinstance(body, string):
+ coding = content.properties.get('content_encoding', None)
+ if coding is None:
+ coding = content.properties['content_encoding'] = 'UTF-8'
+
+ body = body.encode(coding)
+ properties = content._serialize_properties()
+
+ write_frame(1, channel, payload)
+
+ if content:
+ payload = pack('>HHQ', method_sig[0], 0, len(body)) + properties
+
+ write_frame(2, channel, payload)
+
+ chunk_size = self.frame_max - 8
+ for i in range(0, len(body), chunk_size):
+ write_frame(3, channel, body[i:i + chunk_size])
+ self.bytes_sent += 1
diff --git a/amqp/protocol.py b/amqp/protocol.py
new file mode 100644
index 0000000..0856eb4
--- /dev/null
+++ b/amqp/protocol.py
@@ -0,0 +1,13 @@
+from __future__ import absolute_import
+
+from collections import namedtuple
+
+
+queue_declare_ok_t = namedtuple(
+ 'queue_declare_ok_t', ('queue', 'message_count', 'consumer_count'),
+)
+
+basic_return_t = namedtuple(
+ 'basic_return_t',
+ ('reply_code', 'reply_text', 'exchange', 'routing_key', 'message'),
+)
diff --git a/amqp/serialization.py b/amqp/serialization.py
new file mode 100644
index 0000000..4ad1b06
--- /dev/null
+++ b/amqp/serialization.py
@@ -0,0 +1,510 @@
+"""
+Convert between bytestreams and higher-level AMQP types.
+
+2007-11-05 Barry Pederson <bp@barryp.org>
+
+"""
+# Copyright (C) 2007 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+from __future__ import absolute_import
+
+import sys
+
+from datetime import datetime
+from decimal import Decimal
+from io import BytesIO
+from struct import pack, unpack
+from time import mktime
+
+from .exceptions import FrameSyntaxError
+from .five import int_types, long_t, string, string_t, items
+
+IS_PY3K = sys.version_info[0] >= 3
+
+if IS_PY3K:
+ def byte(n):
+ return bytes([n])
+else:
+ byte = chr
+
+
+ILLEGAL_TABLE_TYPE_WITH_KEY = """\
+Table type {0!r} for key {1!r} not handled by amqp. [value: {2!r}]
+"""
+
+ILLEGAL_TABLE_TYPE = """\
+ Table type {0!r} not handled by amqp. [value: {1!r}]
+"""
+
+
+class AMQPReader(object):
+ """Read higher-level AMQP types from a bytestream."""
+ def __init__(self, source):
+ """Source should be either a file-like object with a read() method, or
+ a plain (non-unicode) string."""
+ if isinstance(source, bytes):
+ self.input = BytesIO(source)
+ elif hasattr(source, 'read'):
+ self.input = source
+ else:
+ raise ValueError(
+ 'AMQPReader needs a file-like object or plain string')
+
+ self.bitcount = self.bits = 0
+
+ def close(self):
+ self.input.close()
+
+ def read(self, n):
+ """Read n bytes."""
+ self.bitcount = self.bits = 0
+ return self.input.read(n)
+
+ def read_bit(self):
+ """Read a single boolean value."""
+ if not self.bitcount:
+ self.bits = ord(self.input.read(1))
+ self.bitcount = 8
+ result = (self.bits & 1) == 1
+ self.bits >>= 1
+ self.bitcount -= 1
+ return result
+
+ def read_octet(self):
+ """Read one byte, return as an integer"""
+ self.bitcount = self.bits = 0
+ return unpack('B', self.input.read(1))[0]
+
+ def read_short(self):
+ """Read an unsigned 16-bit integer"""
+ self.bitcount = self.bits = 0
+ return unpack('>H', self.input.read(2))[0]
+
+ def read_long(self):
+ """Read an unsigned 32-bit integer"""
+ self.bitcount = self.bits = 0
+ return unpack('>I', self.input.read(4))[0]
+
+ def read_longlong(self):
+ """Read an unsigned 64-bit integer"""
+ self.bitcount = self.bits = 0
+ return unpack('>Q', self.input.read(8))[0]
+
+ def read_float(self):
+ """Read float value."""
+ self.bitcount = self.bits = 0
+ return unpack('>d', self.input.read(8))[0]
+
+ def read_shortstr(self):
+ """Read a short string that's stored in up to 255 bytes.
+
+ The encoding isn't specified in the AMQP spec, so
+ assume it's utf-8
+
+ """
+ self.bitcount = self.bits = 0
+ slen = unpack('B', self.input.read(1))[0]
+ return self.input.read(slen).decode('utf-8')
+
+ def read_longstr(self):
+ """Read a string that's up to 2**32 bytes.
+
+ The encoding isn't specified in the AMQP spec, so
+ assume it's utf-8
+
+ """
+ self.bitcount = self.bits = 0
+ slen = unpack('>I', self.input.read(4))[0]
+ return self.input.read(slen).decode('utf-8')
+
+ def read_table(self):
+ """Read an AMQP table, and return as a Python dictionary."""
+ self.bitcount = self.bits = 0
+ tlen = unpack('>I', self.input.read(4))[0]
+ table_data = AMQPReader(self.input.read(tlen))
+ result = {}
+ while table_data.input.tell() < tlen:
+ name = table_data.read_shortstr()
+ val = table_data.read_item()
+ result[name] = val
+ return result
+
+ def read_item(self):
+ ftype = ord(self.input.read(1))
+
+ # 'S': long string
+ if ftype == 83:
+ val = self.read_longstr()
+ # 's': short string
+ elif ftype == 115:
+ val = self.read_shortstr()
+ # 'b': short-short int
+ elif ftype == 98:
+ val, = unpack('>B', self.input.read(1))
+ # 'B': short-short unsigned int
+ elif ftype == 66:
+ val, = unpack('>b', self.input.read(1))
+ # 'U': short int
+ elif ftype == 85:
+ val, = unpack('>h', self.input.read(2))
+ # 'u': short unsigned int
+ elif ftype == 117:
+ val, = unpack('>H', self.input.read(2))
+ # 'I': long int
+ elif ftype == 73:
+ val, = unpack('>i', self.input.read(4))
+ # 'i': long unsigned int
+ elif ftype == 105: # 'l'
+ val, = unpack('>I', self.input.read(4))
+ # 'L': long long int
+ elif ftype == 76:
+ val, = unpack('>q', self.input.read(8))
+ # 'l': long long unsigned int
+ elif ftype == 108:
+ val, = unpack('>Q', self.input.read(8))
+ # 'f': float
+ elif ftype == 102:
+ val, = unpack('>f', self.input.read(4))
+ # 'd': double
+ elif ftype == 100:
+ val = self.read_float()
+ # 'D': decimal
+ elif ftype == 68:
+ d = self.read_octet()
+ n, = unpack('>i', self.input.read(4))
+ val = Decimal(n) / Decimal(10 ** d)
+ # 'F': table
+ elif ftype == 70:
+ val = self.read_table() # recurse
+ # 'A': array
+ elif ftype == 65:
+ val = self.read_array()
+ # 't' (bool)
+ elif ftype == 116:
+ val = self.read_bit()
+ # 'T': timestamp
+ elif ftype == 84:
+ val = self.read_timestamp()
+ # 'V': void
+ elif ftype == 86:
+ val = None
+ else:
+ raise FrameSyntaxError(
+ 'Unknown value in table: {0!r} ({1!r})'.format(
+ ftype, type(ftype)))
+ return val
+
+ def read_array(self):
+ array_length = unpack('>I', self.input.read(4))[0]
+ array_data = AMQPReader(self.input.read(array_length))
+ result = []
+ while array_data.input.tell() < array_length:
+ val = array_data.read_item()
+ result.append(val)
+ return result
+
+ def read_timestamp(self):
+ """Read and AMQP timestamp, which is a 64-bit integer representing
+ seconds since the Unix epoch in 1-second resolution.
+
+ Return as a Python datetime.datetime object,
+ expressed as localtime.
+
+ """
+ return datetime.fromtimestamp(self.read_longlong())
+
+
+class AMQPWriter(object):
+ """Convert higher-level AMQP types to bytestreams."""
+
+ def __init__(self, dest=None):
+ """dest may be a file-type object (with a write() method). If None
+ then a BytesIO is created, and the contents can be accessed with
+ this class's getvalue() method."""
+ self.out = BytesIO() if dest is None else dest
+ self.bits = []
+ self.bitcount = 0
+
+ def _flushbits(self):
+ if self.bits:
+ out = self.out
+ for b in self.bits:
+ out.write(pack('B', b))
+ self.bits = []
+ self.bitcount = 0
+
+ def close(self):
+ """Pass through if possible to any file-like destinations."""
+ try:
+ self.out.close()
+ except AttributeError:
+ pass
+
+ def flush(self):
+ """Pass through if possible to any file-like destinations."""
+ try:
+ self.out.flush()
+ except AttributeError:
+ pass
+
+ def getvalue(self):
+ """Get what's been encoded so far if we're working with a BytesIO."""
+ self._flushbits()
+ return self.out.getvalue()
+
+ def write(self, s):
+ """Write a plain Python string with no special encoding in Python 2.x,
+ or bytes in Python 3.x"""
+ self._flushbits()
+ self.out.write(s)
+
+ def write_bit(self, b):
+ """Write a boolean value."""
+ b = 1 if b else 0
+ shift = self.bitcount % 8
+ if shift == 0:
+ self.bits.append(0)
+ self.bits[-1] |= (b << shift)
+ self.bitcount += 1
+
+ def write_octet(self, n):
+ """Write an integer as an unsigned 8-bit value."""
+ if n < 0 or n > 255:
+ raise FrameSyntaxError(
+ 'Octet {0!r} out of range 0..255'.format(n))
+ self._flushbits()
+ self.out.write(pack('B', n))
+
+ def write_short(self, n):
+ """Write an integer as an unsigned 16-bit value."""
+ if n < 0 or n > 65535:
+ raise FrameSyntaxError(
+ 'Octet {0!r} out of range 0..65535'.format(n))
+ self._flushbits()
+ self.out.write(pack('>H', int(n)))
+
+ def write_long(self, n):
+ """Write an integer as an unsigned2 32-bit value."""
+ if n < 0 or n >= 4294967296:
+ raise FrameSyntaxError(
+ 'Octet {0!r} out of range 0..2**31-1'.format(n))
+ self._flushbits()
+ self.out.write(pack('>I', n))
+
+ def write_longlong(self, n):
+ """Write an integer as an unsigned 64-bit value."""
+ if n < 0 or n >= 18446744073709551616:
+ raise FrameSyntaxError(
+ 'Octet {0!r} out of range 0..2**64-1'.format(n))
+ self._flushbits()
+ self.out.write(pack('>Q', n))
+
+ def write_shortstr(self, s):
+ """Write a string up to 255 bytes long (after any encoding).
+
+ If passed a unicode string, encode with UTF-8.
+
+ """
+ self._flushbits()
+ if isinstance(s, string):
+ s = s.encode('utf-8')
+ if len(s) > 255:
+ raise FrameSyntaxError(
+ 'Shortstring overflow ({0} > 255)'.format(len(s)))
+ self.write_octet(len(s))
+ self.out.write(s)
+
+ def write_longstr(self, s):
+ """Write a string up to 2**32 bytes long after encoding.
+
+ If passed a unicode string, encode as UTF-8.
+
+ """
+ self._flushbits()
+ if isinstance(s, string):
+ s = s.encode('utf-8')
+ self.write_long(len(s))
+ self.out.write(s)
+
+ def write_table(self, d):
+ """Write out a Python dictionary made of up string keys, and values
+ that are strings, signed integers, Decimal, datetime.datetime, or
+ sub-dictionaries following the same constraints."""
+ self._flushbits()
+ table_data = AMQPWriter()
+ for k, v in items(d):
+ table_data.write_shortstr(k)
+ table_data.write_item(v, k)
+ table_data = table_data.getvalue()
+ self.write_long(len(table_data))
+ self.out.write(table_data)
+
+ def write_item(self, v, k=None):
+ if isinstance(v, (string_t, bytes)):
+ if isinstance(v, string):
+ v = v.encode('utf-8')
+ self.write(b'S')
+ self.write_longstr(v)
+ elif isinstance(v, bool):
+ self.write(pack('>cB', b't', int(v)))
+ elif isinstance(v, float):
+ self.write(pack('>cd', b'd', v))
+ elif isinstance(v, int_types):
+ self.write(pack('>ci', b'I', v))
+ elif isinstance(v, Decimal):
+ self.write(b'D')
+ sign, digits, exponent = v.as_tuple()
+ v = 0
+ for d in digits:
+ v = (v * 10) + d
+ if sign:
+ v = -v
+ self.write_octet(-exponent)
+ self.write(pack('>i', v))
+ elif isinstance(v, datetime):
+ self.write(b'T')
+ self.write_timestamp(v)
+ ## FIXME: timezone ?
+ elif isinstance(v, dict):
+ self.write(b'F')
+ self.write_table(v)
+ elif isinstance(v, (list, tuple)):
+ self.write(b'A')
+ self.write_array(v)
+ elif v is None:
+ self.write(b'V')
+ else:
+ err = (ILLEGAL_TABLE_TYPE_WITH_KEY.format(type(v), k, v) if k
+ else ILLEGAL_TABLE_TYPE.format(type(v), v))
+ raise FrameSyntaxError(err)
+
+ def write_array(self, a):
+ array_data = AMQPWriter()
+ for v in a:
+ array_data.write_item(v)
+ array_data = array_data.getvalue()
+ self.write_long(len(array_data))
+ self.out.write(array_data)
+
+ def write_timestamp(self, v):
+ """Write out a Python datetime.datetime object as a 64-bit integer
+ representing seconds since the Unix epoch."""
+ self.out.write(pack('>q', long_t(mktime(v.timetuple()))))
+
+
+class GenericContent(object):
+ """Abstract base class for AMQP content.
+
+ Subclasses should override the PROPERTIES attribute.
+
+ """
+ PROPERTIES = [('dummy', 'shortstr')]
+
+ def __init__(self, **props):
+ """Save the properties appropriate to this AMQP content type
+ in a 'properties' dictionary."""
+ d = {}
+ for propname, _ in self.PROPERTIES:
+ if propname in props:
+ d[propname] = props[propname]
+ # FIXME: should we ignore unknown properties?
+
+ self.properties = d
+
+ def __eq__(self, other):
+ """Check if this object has the same properties as another
+ content object."""
+ try:
+ return self.properties == other.properties
+ except AttributeError:
+ return NotImplemented
+
+ def __getattr__(self, name):
+ """Look for additional properties in the 'properties'
+ dictionary, and if present - the 'delivery_info'
+ dictionary."""
+ if name == '__setstate__':
+ # Allows pickling/unpickling to work
+ raise AttributeError('__setstate__')
+
+ if name in self.properties:
+ return self.properties[name]
+
+ if 'delivery_info' in self.__dict__ \
+ and name in self.delivery_info:
+ return self.delivery_info[name]
+
+ raise AttributeError(name)
+
+ def _load_properties(self, raw_bytes):
+ """Given the raw bytes containing the property-flags and property-list
+ from a content-frame-header, parse and insert into a dictionary
+ stored in this object as an attribute named 'properties'."""
+ r = AMQPReader(raw_bytes)
+
+ #
+ # Read 16-bit shorts until we get one with a low bit set to zero
+ #
+ flags = []
+ while 1:
+ flag_bits = r.read_short()
+ flags.append(flag_bits)
+ if flag_bits & 1 == 0:
+ break
+
+ shift = 0
+ d = {}
+ for key, proptype in self.PROPERTIES:
+ if shift == 0:
+ if not flags:
+ break
+ flag_bits, flags = flags[0], flags[1:]
+ shift = 15
+ if flag_bits & (1 << shift):
+ d[key] = getattr(r, 'read_' + proptype)()
+ shift -= 1
+
+ self.properties = d
+
+ def _serialize_properties(self):
+ """serialize the 'properties' attribute (a dictionary) into
+ the raw bytes making up a set of property flags and a
+ property list, suitable for putting into a content frame header."""
+ shift = 15
+ flag_bits = 0
+ flags = []
+ raw_bytes = AMQPWriter()
+ for key, proptype in self.PROPERTIES:
+ val = self.properties.get(key, None)
+ if val is not None:
+ if shift == 0:
+ flags.append(flag_bits)
+ flag_bits = 0
+ shift = 15
+
+ flag_bits |= (1 << shift)
+ if proptype != 'bit':
+ getattr(raw_bytes, 'write_' + proptype)(val)
+
+ shift -= 1
+
+ flags.append(flag_bits)
+ result = AMQPWriter()
+ for flag_bits in flags:
+ result.write_short(flag_bits)
+ result.write(raw_bytes.getvalue())
+
+ return result.getvalue()
diff --git a/amqp/transport.py b/amqp/transport.py
new file mode 100644
index 0000000..eac3dbc
--- /dev/null
+++ b/amqp/transport.py
@@ -0,0 +1,294 @@
+# Copyright (C) 2009 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+from __future__ import absolute_import
+
+import errno
+import re
+import socket
+import ssl
+
+# Jython does not have this attribute
+try:
+ from socket import SOL_TCP
+except ImportError: # pragma: no cover
+ from socket import IPPROTO_TCP as SOL_TCP # noqa
+
+try:
+ from ssl import SSLError
+except ImportError:
+ class SSLError(Exception): # noqa
+ pass
+
+from struct import pack, unpack
+
+from .exceptions import UnexpectedFrame
+from .utils import get_errno, set_cloexec
+
+_UNAVAIL = errno.EAGAIN, errno.EINTR, errno.ENOENT
+
+AMQP_PORT = 5672
+
+EMPTY_BUFFER = bytes()
+
+# Yes, Advanced Message Queuing Protocol Protocol is redundant
+AMQP_PROTOCOL_HEADER = 'AMQP\x01\x01\x00\x09'.encode('latin_1')
+
+# Match things like: [fe80::1]:5432, from RFC 2732
+IPV6_LITERAL = re.compile(r'\[([\.0-9a-f:]+)\](?::(\d+))?')
+
+
+class _AbstractTransport(object):
+ """Common superclass for TCP and SSL transports"""
+ connected = False
+
+ def __init__(self, host, connect_timeout):
+ self.connected = True
+ msg = None
+ port = AMQP_PORT
+
+ m = IPV6_LITERAL.match(host)
+ if m:
+ host = m.group(1)
+ if m.group(2):
+ port = int(m.group(2))
+ else:
+ if ':' in host:
+ host, port = host.rsplit(':', 1)
+ port = int(port)
+
+ self.sock = None
+ last_err = None
+ for res in socket.getaddrinfo(host, port, 0,
+ socket.SOCK_STREAM, SOL_TCP):
+ af, socktype, proto, canonname, sa = res
+ try:
+ self.sock = socket.socket(af, socktype, proto)
+ try:
+ set_cloexec(self.sock, True)
+ except NotImplementedError:
+ pass
+ self.sock.settimeout(connect_timeout)
+ self.sock.connect(sa)
+ except socket.error as exc:
+ msg = exc
+ self.sock.close()
+ self.sock = None
+ last_err = msg
+ continue
+ break
+
+ if not self.sock:
+ # Didn't connect, return the most recent error message
+ raise socket.error(last_err)
+
+ try:
+ self.sock.settimeout(None)
+ self.sock.setsockopt(SOL_TCP, socket.TCP_NODELAY, 1)
+ self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+
+ self._setup_transport()
+
+ self._write(AMQP_PROTOCOL_HEADER)
+ except (OSError, IOError, socket.error) as exc:
+ if get_errno(exc) not in _UNAVAIL:
+ self.connected = False
+ raise
+
+ def __del__(self):
+ try:
+ # socket module may have been collected by gc
+ # if this is called by a thread at shutdown.
+ if socket is not None:
+ try:
+ self.close()
+ except socket.error:
+ pass
+ finally:
+ self.sock = None
+
+ def _read(self, n, initial=False):
+ """Read exactly n bytes from the peer"""
+ raise NotImplementedError('Must be overriden in subclass')
+
+ def _setup_transport(self):
+ """Do any additional initialization of the class (used
+ by the subclasses)."""
+ pass
+
+ def _shutdown_transport(self):
+ """Do any preliminary work in shutting down the connection."""
+ pass
+
+ def _write(self, s):
+ """Completely write a string to the peer."""
+ raise NotImplementedError('Must be overriden in subclass')
+
+ def close(self):
+ if self.sock is not None:
+ self._shutdown_transport()
+ # Call shutdown first to make sure that pending messages
+ # reach the AMQP broker if the program exits after
+ # calling this method.
+ self.sock.shutdown(socket.SHUT_RDWR)
+ self.sock.close()
+ self.sock = None
+ self.connected = False
+
+ def read_frame(self, unpack=unpack):
+ read = self._read
+ try:
+ frame_type, channel, size = unpack('>BHI', read(7, True))
+ payload = read(size)
+ ch = ord(read(1))
+ except socket.timeout:
+ raise
+ except (OSError, IOError, socket.error) as exc:
+ # Don't disconnect for ssl read time outs
+ # http://bugs.python.org/issue10272
+ if isinstance(exc, SSLError) and 'timed out' in str(exc):
+ raise socket.timeout()
+ if get_errno(exc) not in _UNAVAIL:
+ self.connected = False
+ raise
+ if ch == 206: # '\xce'
+ return frame_type, channel, payload
+ else:
+ raise UnexpectedFrame(
+ 'Received 0x{0:02x} while expecting 0xce'.format(ch))
+
+ def write_frame(self, frame_type, channel, payload):
+ size = len(payload)
+ try:
+ self._write(pack(
+ '>BHI%dsB' % size,
+ frame_type, channel, size, payload, 0xce,
+ ))
+ except socket.timeout:
+ raise
+ except (OSError, IOError, socket.error) as exc:
+ if get_errno(exc) not in _UNAVAIL:
+ self.connected = False
+ raise
+
+
+class SSLTransport(_AbstractTransport):
+ """Transport that works over SSL"""
+
+ def __init__(self, host, connect_timeout, ssl):
+ if isinstance(ssl, dict):
+ self.sslopts = ssl
+ self._read_buffer = EMPTY_BUFFER
+ super(SSLTransport, self).__init__(host, connect_timeout)
+
+ def _setup_transport(self):
+ """Wrap the socket in an SSL object."""
+ if hasattr(self, 'sslopts'):
+ self.sock = ssl.wrap_socket(self.sock, **self.sslopts)
+ else:
+ self.sock = ssl.wrap_socket(self.sock)
+ self.sock.do_handshake()
+ self._quick_recv = self.sock.read
+
+ def _shutdown_transport(self):
+ """Unwrap a Python 2.6 SSL socket, so we can call shutdown()"""
+ if self.sock is not None:
+ try:
+ unwrap = self.sock.unwrap
+ except AttributeError:
+ return
+ self.sock = unwrap()
+
+ def _read(self, n, initial=False,
+ _errnos=(errno.ENOENT, errno.EAGAIN, errno.EINTR)):
+ # According to SSL_read(3), it can at most return 16kb of data.
+ # Thus, we use an internal read buffer like TCPTransport._read
+ # to get the exact number of bytes wanted.
+ recv = self._quick_recv
+ rbuf = self._read_buffer
+ try:
+ while len(rbuf) < n:
+ try:
+ s = recv(n - len(rbuf)) # see note above
+ except socket.error as exc:
+ # ssl.sock.read may cause ENOENT if the
+ # operation couldn't be performed (Issue celery#1414).
+ if not initial and exc.errno in _errnos:
+ continue
+ raise
+ if not s:
+ raise IOError('Socket closed')
+ rbuf += s
+ except:
+ self._read_buffer = rbuf
+ raise
+ result, self._read_buffer = rbuf[:n], rbuf[n:]
+ return result
+
+ def _write(self, s):
+ """Write a string out to the SSL socket fully."""
+ try:
+ write = self.sock.write
+ except AttributeError:
+ # Works around a bug in python socket library
+ raise IOError('Socket closed')
+ else:
+ while s:
+ n = write(s)
+ if not n:
+ raise IOError('Socket closed')
+ s = s[n:]
+
+
+class TCPTransport(_AbstractTransport):
+ """Transport that deals directly with TCP socket."""
+
+ def _setup_transport(self):
+ """Setup to _write() directly to the socket, and
+ do our own buffered reads."""
+ self._write = self.sock.sendall
+ self._read_buffer = EMPTY_BUFFER
+ self._quick_recv = self.sock.recv
+
+ def _read(self, n, initial=False, _errnos=(errno.EAGAIN, errno.EINTR)):
+ """Read exactly n bytes from the socket"""
+ recv = self._quick_recv
+ rbuf = self._read_buffer
+ try:
+ while len(rbuf) < n:
+ try:
+ s = recv(n - len(rbuf))
+ except socket.error as exc:
+ if not initial and exc.errno in _errnos:
+ continue
+ raise
+ if not s:
+ raise IOError('Socket closed')
+ rbuf += s
+ except:
+ self._read_buffer = rbuf
+ raise
+
+ result, self._read_buffer = rbuf[:n], rbuf[n:]
+ return result
+
+
+def create_transport(host, connect_timeout, ssl=False):
+ """Given a few parameters from the Connection constructor,
+ select and create a subclass of _AbstractTransport."""
+ if ssl:
+ return SSLTransport(host, connect_timeout, ssl)
+ else:
+ return TCPTransport(host, connect_timeout)
diff --git a/amqp/utils.py b/amqp/utils.py
new file mode 100644
index 0000000..900d2aa
--- /dev/null
+++ b/amqp/utils.py
@@ -0,0 +1,102 @@
+from __future__ import absolute_import
+
+import sys
+
+try:
+ import fcntl
+except ImportError:
+ fcntl = None # noqa
+
+
+class promise(object):
+ if not hasattr(sys, 'pypy_version_info'):
+ __slots__ = tuple(
+ 'fun args kwargs value ready failed '
+ ' on_success on_error calls'.split()
+ )
+
+ def __init__(self, fun, args=(), kwargs=(),
+ on_success=None, on_error=None):
+ self.fun = fun
+ self.args = args
+ self.kwargs = kwargs
+ self.ready = False
+ self.failed = False
+ self.on_success = on_success
+ self.on_error = on_error
+ self.value = None
+ self.calls = 0
+
+ def __repr__(self):
+ return '<$: {0.fun.__name__}(*{0.args!r}, **{0.kwargs!r})'.format(
+ self,
+ )
+
+ def __call__(self, *args, **kwargs):
+ try:
+ self.value = self.fun(
+ *self.args + args if self.args else args,
+ **dict(self.kwargs, **kwargs) if self.kwargs else kwargs
+ )
+ except Exception as exc:
+ self.set_error_state(exc)
+ else:
+ if self.on_success:
+ self.on_success(self.value)
+ finally:
+ self.ready = True
+ self.calls += 1
+
+ def then(self, callback=None, on_error=None):
+ self.on_success = callback
+ self.on_error = on_error
+ return callback
+
+ def set_error_state(self, exc):
+ self.failed = True
+ if self.on_error is None:
+ raise
+ self.on_error(exc)
+
+ def throw(self, exc):
+ try:
+ raise exc
+ except exc.__class__ as with_cause:
+ self.set_error_state(with_cause)
+
+
+def noop():
+ return promise(lambda *a, **k: None)
+
+
+try:
+ from os import set_cloexec # Python 3.4?
+except ImportError:
+ def set_cloexec(fd, cloexec): # noqa
+ try:
+ FD_CLOEXEC = fcntl.FD_CLOEXEC
+ except AttributeError:
+ raise NotImplementedError(
+ 'close-on-exec flag not supported on this platform',
+ )
+ flags = fcntl.fcntl(fd, fcntl.F_GETFD)
+ if cloexec:
+ flags |= FD_CLOEXEC
+ else:
+ flags &= ~FD_CLOEXEC
+ return fcntl.fcntl(fd, fcntl.F_SETFD, flags)
+
+
+def get_errno(exc):
+ """:exc:`socket.error` and :exc:`IOError` first got
+ the ``.errno`` attribute in Py2.7"""
+ try:
+ return exc.errno
+ except AttributeError:
+ try:
+ # e.args = (errno, reason)
+ if isinstance(exc.args, tuple) and len(exc.args) == 2:
+ return exc.args[0]
+ except AttributeError:
+ pass
+ return 0
diff --git a/demo/amqp_clock.py b/demo/amqp_clock.py
new file mode 100755
index 0000000..c718266
--- /dev/null
+++ b/demo/amqp_clock.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+"""
+AMQP Clock
+
+Fires off simple messages at one-minute intervals to a topic
+exchange named 'clock', with the topic of the message being
+the local time as 'year.month.date.dow.hour.minute',
+for example: '2007.11.26.1.12.33', where the dow (day of week)
+is 0 for Sunday, 1 for Monday, and so on (similar to Unix crontab).
+
+A consumer could then bind a queue to the routing key '#.0'
+for example to get a message at the beginning of each hour.
+
+2007-11-26 Barry Pederson <bp@barryp.org>
+
+"""
+from datetime import datetime
+from optparse import OptionParser
+from time import sleep
+
+import amqp
+Message = amqp.Message
+
+EXCHANGE_NAME = 'clock'
+TOPIC_PATTERN = '%Y.%m.%d.%w.%H.%M' # Python datetime.strftime() pattern
+
+
+def main():
+ parser = OptionParser()
+ parser.add_option(
+ '--host', dest='host',
+ help='AMQP server to connect to (default: %default)',
+ default='localhost',
+ )
+ parser.add_option(
+ '-u', '--userid', dest='userid',
+ help='AMQP userid to authenticate as (default: %default)',
+ default='guest',
+ )
+ parser.add_option(
+ '-p', '--password', dest='password',
+ help='AMQP password to authenticate with (default: %default)',
+ default='guest',
+ )
+ parser.add_option(
+ '--ssl', dest='ssl', action='store_true',
+ help='Enable SSL with AMQP server (default: not enabled)',
+ default=False,
+ )
+
+ options, args = parser.parse_args()
+
+ conn = amqp.Connection(options.host, options.userid, options.password)
+ ch = conn.channel()
+ ch.exchange_declare(EXCHANGE_NAME, type='topic')
+
+ # Make sure our first message is close to the beginning
+ # of a minute
+ now = datetime.now()
+ if now.second > 0:
+ sleep(60 - now.second)
+
+ while True:
+ now = datetime.now()
+ msg = Message(timestamp=now)
+ msg_topic = now.strftime(TOPIC_PATTERN)
+ ch.basic_publish(msg, EXCHANGE_NAME, routing_key=msg_topic)
+
+ # Don't know how long the basic_publish took, so
+ # grab the time again.
+ now = datetime.now()
+ sleep(60 - now.second)
+
+ ch.close()
+ conn.close()
+
+if __name__ == '__main__':
+ main()
diff --git a/demo/demo_receive.py b/demo/demo_receive.py
new file mode 100755
index 0000000..bfda624
--- /dev/null
+++ b/demo/demo_receive.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+"""
+Test AMQP library.
+
+Repeatedly receive messages from the demo_send.py
+script, until it receives a message with 'quit' as the body.
+
+2007-11-11 Barry Pederson <bp@barryp.org>
+
+"""
+from optparse import OptionParser
+from functools import partial
+
+import amqp
+
+
+def callback(channel, msg):
+ for key, val in msg.properties.items():
+ print('%s: %s' % (key, str(val)))
+ for key, val in msg.delivery_info.items():
+ print('> %s: %s' % (key, str(val)))
+
+ print('')
+ print(msg.body)
+ print('-------')
+ print(msg.delivery_tag)
+ channel.basic_ack(msg.delivery_tag)
+
+ #
+ # Cancel this callback
+ #
+ if msg.body == 'quit':
+ channel.basic_cancel(msg.consumer_tag)
+
+
+def main():
+ parser = OptionParser()
+ parser.add_option(
+ '--host', dest='host',
+ help='AMQP server to connect to (default: %default)',
+ default='localhost',
+ )
+ parser.add_option(
+ '-u', '--userid', dest='userid',
+ help='userid to authenticate as (default: %default)',
+ default='guest',
+ )
+ parser.add_option(
+ '-p', '--password', dest='password',
+ help='password to authenticate with (default: %default)',
+ default='guest',
+ )
+ parser.add_option(
+ '--ssl', dest='ssl', action='store_true',
+ help='Enable SSL (default: not enabled)',
+ default=False,
+ )
+
+ options, args = parser.parse_args()
+
+ conn = amqp.Connection(options.host, userid=options.userid,
+ password=options.password, ssl=options.ssl)
+
+ ch = conn.channel()
+
+ ch.exchange_declare('myfan', 'fanout')
+ qname, _, _ = ch.queue_declare()
+ ch.queue_bind(qname, 'myfan')
+ ch.basic_consume(qname, callback=partial(callback, ch))
+
+ #pyamqp://
+
+ #
+ # Loop as long as the channel has callbacks registered
+ #
+ while ch.callbacks:
+ ch.wait()
+
+ ch.close()
+ conn.close()
+
+if __name__ == '__main__':
+ main()
diff --git a/demo/demo_send.py b/demo/demo_send.py
new file mode 100755
index 0000000..27bb1b1
--- /dev/null
+++ b/demo/demo_send.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+"""
+Test AMQP library.
+
+Send a message to the corresponding demo_receive.py script, any
+arguments to this program are joined together and sent as a message
+body.
+
+2007-11-11 Barry Pederson <bp@barryp.org>
+
+"""
+import sys
+from optparse import OptionParser
+
+import amqp
+
+
+def main():
+ parser = OptionParser(
+ usage='usage: %prog [options] message\nexample: %prog hello world',
+ )
+ parser.add_option(
+ '--host', dest='host',
+ help='AMQP server to connect to (default: %default)',
+ default='localhost',
+ )
+ parser.add_option(
+ '-u', '--userid', dest='userid',
+ help='userid to authenticate as (default: %default)',
+ default='guest',
+ )
+ parser.add_option(
+ '-p', '--password', dest='password',
+ help='password to authenticate with (default: %default)',
+ default='guest',
+ )
+ parser.add_option(
+ '--ssl', dest='ssl', action='store_true',
+ help='Enable SSL (default: not enabled)',
+ default=False,
+ )
+
+ options, args = parser.parse_args()
+
+ if not args:
+ parser.print_help()
+ sys.exit(1)
+
+ msg_body = ' '.join(args)
+
+ conn = amqp.Connection(options.host, userid=options.userid,
+ password=options.password, ssl=options.ssl)
+
+ ch = conn.channel()
+ ch.exchange_declare('myfan', 'fanout')
+
+ msg = amqp.Message(msg_body, content_type='text/plain',
+ application_headers={'foo': 7, 'bar': 'baz'})
+
+ ch.basic_publish(msg, 'myfan')
+
+ ch.close()
+ conn.close()
+
+if __name__ == '__main__':
+ main()
diff --git a/docs/.static/.keep b/docs/.static/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/docs/.static/.keep
diff --git a/docs/.templates/page.html b/docs/.templates/page.html
new file mode 100644
index 0000000..f01b9d3
--- /dev/null
+++ b/docs/.templates/page.html
@@ -0,0 +1,4 @@
+{% extends "layout.html" %}
+{% block body %}
+ {{ body }}
+{% endblock %}
diff --git a/docs/.templates/sidebarintro.html b/docs/.templates/sidebarintro.html
new file mode 100644
index 0000000..fbab09d
--- /dev/null
+++ b/docs/.templates/sidebarintro.html
@@ -0,0 +1,4 @@
+<p class="logo"><a href="{{ pathto(master_doc) }}">
+ <img class="logo" src="http://cloud.github.com/downloads/celery/celery/celery_128.png" alt="Logo"/>
+</a></p>
+
diff --git a/docs/.templates/sidebarlogo.html b/docs/.templates/sidebarlogo.html
new file mode 100644
index 0000000..0e2da8c
--- /dev/null
+++ b/docs/.templates/sidebarlogo.html
@@ -0,0 +1,3 @@
+<p class="logo"><a href="{{ pathto(master_doc) }}">
+ <img class="logo" src="http://cloud.github.com/downloads/celery/celery/celery_128.png" alt="Logo"/>
+</a></p>
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..1ea219c
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,81 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d .build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html web pickle htmlhelp latex changes linkcheck
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " changes to make an overview over all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+
+clean:
+ -rm -rf .build/*
+
+html:
+ mkdir -p .build/html .build/doctrees
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) .build/html
+ @echo
+ @echo "Build finished. The HTML pages are in .build/html."
+
+coverage:
+ mkdir -p .build/coverage .build/doctrees
+ $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) .build/coverage
+ @echo
+ @echo "Build finished."
+
+pickle:
+ mkdir -p .build/pickle .build/doctrees
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) .build/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+web: pickle
+
+json:
+ mkdir -p .build/json .build/doctrees
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) .build/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ mkdir -p .build/htmlhelp .build/doctrees
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) .build/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in .build/htmlhelp."
+
+latex:
+ mkdir -p .build/latex .build/doctrees
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) .build/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in .build/latex."
+ @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+ "run these through (pdf)latex."
+
+changes:
+ mkdir -p .build/changes .build/doctrees
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) .build/changes
+ @echo
+ @echo "The overview file is in .build/changes."
+
+linkcheck:
+ mkdir -p .build/linkcheck .build/doctrees
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) .build/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in .build/linkcheck/output.txt."
diff --git a/docs/_ext/applyxrefs.py b/docs/_ext/applyxrefs.py
new file mode 100644
index 0000000..deed5d9
--- /dev/null
+++ b/docs/_ext/applyxrefs.py
@@ -0,0 +1,92 @@
+"""Adds xref targets to the top of files."""
+
+import sys
+import os
+
+testing = False
+
+DONT_TOUCH = (
+ './index.txt',
+)
+
+
+def target_name(fn):
+ if fn.endswith('.txt'):
+ fn = fn[:-4]
+ return '_' + fn.lstrip('./').replace('/', '-')
+
+
+def process_file(fn, lines):
+ lines.insert(0, '\n')
+ lines.insert(0, '.. %s:\n' % target_name(fn))
+ try:
+ f = open(fn, 'w')
+ except IOError:
+ print("Can't open %s for writing. Not touching it." % fn)
+ return
+ try:
+ f.writelines(lines)
+ except IOError:
+ print("Can't write to %s. Not touching it." % fn)
+ finally:
+ f.close()
+
+
+def has_target(fn):
+ try:
+ f = open(fn, 'r')
+ except IOError:
+ print("Can't open %s. Not touching it." % fn)
+ return (True, None)
+ readok = True
+ try:
+ lines = f.readlines()
+ except IOError:
+ print("Can't read %s. Not touching it." % fn)
+ readok = False
+ finally:
+ f.close()
+ if not readok:
+ return (True, None)
+
+ #print fn, len(lines)
+ if len(lines) < 1:
+ print("Not touching empty file %s." % fn)
+ return (True, None)
+ if lines[0].startswith('.. _'):
+ return (True, None)
+ return (False, lines)
+
+
+def main(argv=None):
+ if argv is None:
+ argv = sys.argv
+
+ if len(argv) == 1:
+ argv.extend('.')
+
+ files = []
+ for root in argv[1:]:
+ for (dirpath, dirnames, filenames) in os.walk(root):
+ files.extend([(dirpath, f) for f in filenames])
+ files.sort()
+ files = [os.path.join(p, fn) for p, fn in files if fn.endswith('.txt')]
+ #print files
+
+ for fn in files:
+ if fn in DONT_TOUCH:
+ print("Skipping blacklisted file %s." % fn)
+ continue
+
+ target_found, lines = has_target(fn)
+ if not target_found:
+ if testing:
+ print '%s: %s' % (fn, lines[0]),
+ else:
+ print "Adding xref to %s" % fn
+ process_file(fn, lines)
+ else:
+ print "Skipping %s: already has a xref" % fn
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/docs/_ext/literals_to_xrefs.py b/docs/_ext/literals_to_xrefs.py
new file mode 100644
index 0000000..41aa616
--- /dev/null
+++ b/docs/_ext/literals_to_xrefs.py
@@ -0,0 +1,173 @@
+"""
+Runs through a reST file looking for old-style literals, and helps replace them
+with new-style references.
+"""
+
+import re
+import sys
+import shelve
+
+refre = re.compile(r'``([^`\s]+?)``')
+
+ROLES = (
+ 'attr',
+ 'class',
+ "djadmin",
+ 'data',
+ 'exc',
+ 'file',
+ 'func',
+ 'lookup',
+ 'meth',
+ 'mod',
+ "djadminopt",
+ "ref",
+ "setting",
+ "term",
+ "tfilter",
+ "ttag",
+
+ # special
+ "skip",
+)
+
+ALWAYS_SKIP = [
+ "NULL",
+ "True",
+ "False",
+]
+
+
+def fixliterals(fname):
+ data = open(fname).read()
+
+ last = 0
+ new = []
+ storage = shelve.open("/tmp/literals_to_xref.shelve")
+ lastvalues = storage.get("lastvalues", {})
+
+ for m in refre.finditer(data):
+
+ new.append(data[last:m.start()])
+ last = m.end()
+
+ line_start = data.rfind("\n", 0, m.start())
+ line_end = data.find("\n", m.end())
+ prev_start = data.rfind("\n", 0, line_start)
+ next_end = data.find("\n", line_end + 1)
+
+ # Skip always-skip stuff
+ if m.group(1) in ALWAYS_SKIP:
+ new.append(m.group(0))
+ continue
+
+ # skip when the next line is a title
+ next_line = data[m.end():next_end].strip()
+ if next_line[0] in "!-/:-@[-`{-~" and \
+ all(c == next_line[0] for c in next_line):
+ new.append(m.group(0))
+ continue
+
+ sys.stdout.write("\n" + "-" * 80 + "\n")
+ sys.stdout.write(data[prev_start + 1:m.start()])
+ sys.stdout.write(colorize(m.group(0), fg="red"))
+ sys.stdout.write(data[m.end():next_end])
+ sys.stdout.write("\n\n")
+
+ replace_type = None
+ while replace_type is None:
+ replace_type = raw_input(
+ colorize("Replace role: ", fg="yellow")).strip().lower()
+ if replace_type and replace_type not in ROLES:
+ replace_type = None
+
+ if replace_type == "":
+ new.append(m.group(0))
+ continue
+
+ if replace_type == "skip":
+ new.append(m.group(0))
+ ALWAYS_SKIP.append(m.group(1))
+ continue
+
+ default = lastvalues.get(m.group(1), m.group(1))
+ if default.endswith("()") and \
+ replace_type in ("class", "func", "meth"):
+ default = default[:-2]
+ replace_value = raw_input(
+ colorize("Text <target> [", fg="yellow") + default +
+ colorize("]: ", fg="yellow")).strip()
+ if not replace_value:
+ replace_value = default
+ new.append(":%s:`%s`" % (replace_type, replace_value))
+ lastvalues[m.group(1)] = replace_value
+
+ new.append(data[last:])
+ open(fname, "w").write("".join(new))
+
+ storage["lastvalues"] = lastvalues
+ storage.close()
+
+
+def colorize(text='', opts=(), **kwargs):
+ """
+ Returns your text, enclosed in ANSI graphics codes.
+
+ Depends on the keyword arguments 'fg' and 'bg', and the contents of
+ the opts tuple/list.
+
+ Returns the RESET code if no parameters are given.
+
+ Valid colors:
+ 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'
+
+ Valid options:
+ 'bold'
+ 'underscore'
+ 'blink'
+ 'reverse'
+ 'conceal'
+ 'noreset' - string will not be auto-terminated with the RESET code
+
+ Examples:
+ colorize('hello', fg='red', bg='blue', opts=('blink',))
+ colorize()
+ colorize('goodbye', opts=('underscore',))
+ print colorize('first line', fg='red', opts=('noreset',))
+ print 'this should be red too'
+ print colorize('and so should this')
+ print 'this should not be red'
+ """
+ color_names = ('black', 'red', 'green', 'yellow',
+ 'blue', 'magenta', 'cyan', 'white')
+ foreground = dict([(color_names[x], '3%s' % x) for x in range(8)])
+ background = dict([(color_names[x], '4%s' % x) for x in range(8)])
+
+ RESET = '0'
+ opt_dict = {'bold': '1',
+ 'underscore': '4',
+ 'blink': '5',
+ 'reverse': '7',
+ 'conceal': '8'}
+
+ text = str(text)
+ code_list = []
+ if text == '' and len(opts) == 1 and opts[0] == 'reset':
+ return '\x1b[%sm' % RESET
+ for k, v in kwargs.iteritems():
+ if k == 'fg':
+ code_list.append(foreground[v])
+ elif k == 'bg':
+ code_list.append(background[v])
+ for o in opts:
+ if o in opt_dict:
+ code_list.append(opt_dict[o])
+ if 'noreset' not in opts:
+ text = text + '\x1b[%sm' % RESET
+ return ('\x1b[%sm' % ';'.join(code_list)) + text
+
+if __name__ == '__main__':
+ try:
+ fixliterals(sys.argv[1])
+ except (KeyboardInterrupt, SystemExit):
+ print
diff --git a/docs/_theme/celery/static/celery.css_t b/docs/_theme/celery/static/celery.css_t
new file mode 100644
index 0000000..807081a
--- /dev/null
+++ b/docs/_theme/celery/static/celery.css_t
@@ -0,0 +1,401 @@
+/*
+ * celery.css_t
+ * ~~~~~~~~~~~~
+ *
+ * :copyright: Copyright 2010 by Armin Ronacher.
+ * :license: BSD, see LICENSE for details.
+ */
+
+{% set page_width = 940 %}
+{% set sidebar_width = 220 %}
+{% set body_font_stack = 'Optima, Segoe, "Segoe UI", Candara, Calibri, Arial, sans-serif' %}
+{% set headline_font_stack = 'Futura, "Trebuchet MS", Arial, sans-serif' %}
+{% set code_font_stack = "'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace" %}
+
+@import url("basic.css");
+
+/* -- page layout ----------------------------------------------------------- */
+
+body {
+ font-family: {{ body_font_stack }};
+ font-size: 17px;
+ background-color: white;
+ color: #000;
+ margin: 30px 0 0 0;
+ padding: 0;
+}
+
+div.document {
+ width: {{ page_width }}px;
+ margin: 0 auto;
+}
+
+div.deck {
+ font-size: 18px;
+}
+
+p.developmentversion {
+ color: red;
+}
+
+div.related {
+ width: {{ page_width - 20 }}px;
+ padding: 5px 10px;
+ background: #F2FCEE;
+ margin: 15px auto 15px auto;
+}
+
+div.documentwrapper {
+ float: left;
+ width: 100%;
+}
+
+div.bodywrapper {
+ margin: 0 0 0 {{ sidebar_width }}px;
+}
+
+div.sphinxsidebar {
+ width: {{ sidebar_width }}px;
+}
+
+hr {
+ border: 1px solid #B1B4B6;
+}
+
+div.body {
+ background-color: #ffffff;
+ color: #3E4349;
+ padding: 0 30px 0 30px;
+}
+
+img.celerylogo {
+ padding: 0 0 10px 10px;
+ float: right;
+}
+
+div.footer {
+ width: {{ page_width - 15 }}px;
+ margin: 10px auto 30px auto;
+ padding-right: 15px;
+ font-size: 14px;
+ color: #888;
+ text-align: right;
+}
+
+div.footer a {
+ color: #888;
+}
+
+div.sphinxsidebar a {
+ color: #444;
+ text-decoration: none;
+ border-bottom: 1px dashed #DCF0D5;
+}
+
+div.sphinxsidebar a:hover {
+ border-bottom: 1px solid #999;
+}
+
+div.sphinxsidebar {
+ font-size: 14px;
+ line-height: 1.5;
+}
+
+div.sphinxsidebarwrapper {
+ padding: 7px 10px;
+}
+
+div.sphinxsidebarwrapper p.logo {
+ padding: 0 0 20px 0;
+ margin: 0;
+}
+
+div.sphinxsidebar h3,
+div.sphinxsidebar h4 {
+ font-family: {{ headline_font_stack }};
+ color: #444;
+ font-size: 24px;
+ font-weight: normal;
+ margin: 0 0 5px 0;
+ padding: 0;
+}
+
+div.sphinxsidebar h4 {
+ font-size: 20px;
+}
+
+div.sphinxsidebar h3 a {
+ color: #444;
+}
+
+div.sphinxsidebar p.logo a,
+div.sphinxsidebar h3 a,
+div.sphinxsidebar p.logo a:hover,
+div.sphinxsidebar h3 a:hover {
+ border: none;
+}
+
+div.sphinxsidebar p {
+ color: #555;
+ margin: 10px 0;
+}
+
+div.sphinxsidebar ul {
+ margin: 10px 0;
+ padding: 0;
+ color: #000;
+}
+
+div.sphinxsidebar input {
+ border: 1px solid #ccc;
+ font-family: {{ body_font_stack }};
+ font-size: 1em;
+}
+
+/* -- body styles ----------------------------------------------------------- */
+
+a {
+ color: #348613;
+ text-decoration: underline;
+}
+
+a:hover {
+ color: #59B833;
+ text-decoration: underline;
+}
+
+div.body h1,
+div.body h2,
+div.body h3,
+div.body h4,
+div.body h5,
+div.body h6 {
+ font-family: {{ headline_font_stack }};
+ font-weight: normal;
+ margin: 30px 0px 10px 0px;
+ padding: 0;
+}
+
+div.body h1 { margin-top: 0; padding-top: 0; font-size: 200%; }
+div.body h2 { font-size: 180%; }
+div.body h3 { font-size: 150%; }
+div.body h4 { font-size: 130%; }
+div.body h5 { font-size: 100%; }
+div.body h6 { font-size: 100%; }
+
+div.body h1 a.toc-backref,
+div.body h2 a.toc-backref,
+div.body h3 a.toc-backref,
+div.body h4 a.toc-backref,
+div.body h5 a.toc-backref,
+div.body h6 a.toc-backref {
+ color: inherit!important;
+ text-decoration: none;
+}
+
+a.headerlink {
+ color: #ddd;
+ padding: 0 4px;
+ text-decoration: none;
+}
+
+a.headerlink:hover {
+ color: #444;
+ background: #eaeaea;
+}
+
+div.body p, div.body dd, div.body li {
+ line-height: 1.4em;
+}
+
+div.admonition {
+ background: #fafafa;
+ margin: 20px -30px;
+ padding: 10px 30px;
+ border-top: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+}
+
+div.admonition p.admonition-title {
+ font-family: {{ headline_font_stack }};
+ font-weight: normal;
+ font-size: 24px;
+ margin: 0 0 10px 0;
+ padding: 0;
+ line-height: 1;
+}
+
+div.admonition p.last {
+ margin-bottom: 0;
+}
+
+div.highlight{
+ background-color: white;
+}
+
+dt:target, .highlight {
+ background: #FAF3E8;
+}
+
+div.note {
+ background-color: #eee;
+ border: 1px solid #ccc;
+}
+
+div.seealso {
+ background-color: #ffc;
+ border: 1px solid #ff6;
+}
+
+div.topic {
+ background-color: #eee;
+}
+
+div.warning {
+ background-color: #ffe4e4;
+ border: 1px solid #f66;
+}
+
+p.admonition-title {
+ display: inline;
+}
+
+p.admonition-title:after {
+ content: ":";
+}
+
+pre, tt {
+ font-family: {{ code_font_stack }};
+ font-size: 0.9em;
+}
+
+img.screenshot {
+}
+
+tt.descname, tt.descclassname {
+ font-size: 0.95em;
+}
+
+tt.descname {
+ padding-right: 0.08em;
+}
+
+img.screenshot {
+ -moz-box-shadow: 2px 2px 4px #eee;
+ -webkit-box-shadow: 2px 2px 4px #eee;
+ box-shadow: 2px 2px 4px #eee;
+}
+
+table.docutils {
+ border: 1px solid #888;
+ -moz-box-shadow: 2px 2px 4px #eee;
+ -webkit-box-shadow: 2px 2px 4px #eee;
+ box-shadow: 2px 2px 4px #eee;
+}
+
+table.docutils td, table.docutils th {
+ border: 1px solid #888;
+ padding: 0.25em 0.7em;
+}
+
+table.field-list, table.footnote {
+ border: none;
+ -moz-box-shadow: none;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+
+table.footnote {
+ margin: 15px 0;
+ width: 100%;
+ border: 1px solid #eee;
+ background: #fdfdfd;
+ font-size: 0.9em;
+}
+
+table.footnote + table.footnote {
+ margin-top: -15px;
+ border-top: none;
+}
+
+table.field-list th {
+ padding: 0 0.8em 0 0;
+}
+
+table.field-list td {
+ padding: 0;
+}
+
+table.footnote td.label {
+ width: 0px;
+ padding: 0.3em 0 0.3em 0.5em;
+}
+
+table.footnote td {
+ padding: 0.3em 0.5em;
+}
+
+dl {
+ margin: 0;
+ padding: 0;
+}
+
+dl dd {
+ margin-left: 30px;
+}
+
+blockquote {
+ margin: 0 0 0 30px;
+ padding: 0;
+}
+
+ul {
+ margin: 10px 0 10px 30px;
+ padding: 0;
+}
+
+pre {
+ background: #F0FFEB;
+ padding: 7px 10px;
+ margin: 15px 0;
+ border: 1px solid #C7ECB8;
+ border-radius: 2px;
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
+ line-height: 1.3em;
+}
+
+tt {
+ background: #F0FFEB;
+ color: #222;
+ /* padding: 1px 2px; */
+}
+
+tt.xref, a tt {
+ background: #F0FFEB;
+ border-bottom: 1px solid white;
+}
+
+a.reference {
+ text-decoration: none;
+ border-bottom: 1px dashed #DCF0D5;
+}
+
+a.reference:hover {
+ border-bottom: 1px solid #6D4100;
+}
+
+a.footnote-reference {
+ text-decoration: none;
+ font-size: 0.7em;
+ vertical-align: top;
+ border-bottom: 1px dashed #DCF0D5;
+}
+
+a.footnote-reference:hover {
+ border-bottom: 1px solid #6D4100;
+}
+
+a:hover tt {
+ background: #EEE;
+}
diff --git a/docs/_theme/celery/theme.conf b/docs/_theme/celery/theme.conf
new file mode 100644
index 0000000..9ad052c
--- /dev/null
+++ b/docs/_theme/celery/theme.conf
@@ -0,0 +1,5 @@
+[theme]
+inherit = basic
+stylesheet = celery.css
+
+[options]
diff --git a/docs/changelog.rst b/docs/changelog.rst
new file mode 100644
index 0000000..e036942
--- /dev/null
+++ b/docs/changelog.rst
@@ -0,0 +1,484 @@
+Changes
+=======
+
+py-amqp is fork of amqplib used by Kombu containing additional features and improvements.
+The previous amqplib changelog is here:
+http://code.google.com/p/py-amqplib/source/browse/CHANGES
+
+.. _version-1.4.5:
+
+1.4.5
+=====
+:release-date: 2014-04-15 09:00 P.M UTC
+:release-by: Ask Solem
+
+- Can now deserialize more AMQP types.
+
+ Now handles types ``short string``, ``short short int``,
+ ``short short unsigned int``, ``short int``, ``short unsigned int``,
+ ``long unsigned int``, ``long long int``, ``long long unsigned int``
+ and ``float`` which for some reason was missing, even in the original
+ amqplib module.
+
+- SSL: Workaround for Python SSL bug.
+
+ A bug in the python socket library causes ``ssl.read/write()``
+ on a closed socket to raise :exc:`AttributeError` instead of
+ :exc:`IOError`.
+
+ Fix contributed by Craig Jellick.
+
+- ``Transport.__del_`` now handles errors occurring at late interpreter
+ shutdown (Issue #36).
+
+.. _version-1.4.4:
+
+1.4.4
+=====
+:release-date: 2014-03-03 04:00 P.M UTC
+:release-by: Ask Solem
+
+- SSL transport accidentally disconnected after read timeout.
+
+ Fix contributed by Craig Jellick.
+
+.. _version-1.4.3:
+
+1.4.3
+=====
+:release-date: 2014-02-09 03:00 P.M UTC
+:release-by: Ask Solem
+
+- Fixed bug where more data was requested from the socket
+ than was actually needed.
+
+ Contributed by Ionel Cristian Mărieș.
+
+.. _version-1.4.2:
+
+1.4.2
+=====
+:release-date: 2014-01-23 05:00 P.M UTC
+
+- Heartbeat negotiation would use heartbeat value from server even
+ if heartbeat disabled (Issue #31).
+
+.. _version-1.4.1:
+
+1.4.1
+=====
+:release-date: 2014-01-14 09:30 P.M UTC
+:release-by: Ask Solem
+
+- Fixed error occurring when heartbeats disabled.
+
+.. _version-1.4.0:
+
+1.4.0
+=====
+:release-date: 2014-01-13 03:00 P.M UTC
+:release-by: Ask Solem
+
+- Heartbeat implementation improved (Issue #6).
+
+ The new heartbeat behavior is the same approach as taken by the
+ RabbitMQ java library.
+
+ This also means that clients should preferably call the ``heartbeat_tick``
+ method more frequently (like every second) instead of using the old
+ ``rate`` argument (which is now ignored).
+
+ - Heartbeat interval is negotiated with the server.
+ - Some delay is allowed if the heartbeat is late.
+ - Monotonic time is used to keep track of the heartbeat
+ instead of relying on the caller to call the checking function
+ at the right time.
+
+ Contributed by Dustin J. Mitchell.
+
+- NoneType is now supported in tables and arrays.
+
+ Contributed by Dominik Fässler.
+
+- SSLTransport: Now handles ``ENOENT``.
+
+ Fix contributed by Adrien Guinet.
+
+.. _version-1.3.3:
+
+1.3.3
+=====
+:release-date: 2013-11-11 03:30 P.M UTC
+:release-by: Ask Solem
+
+- SSLTransport: Now keeps read buffer if an exception is raised
+ (Issue #26).
+
+ Fix contributed by Tommie Gannert.
+
+.. _version-1.3.2:
+
+1.3.2
+=====
+:release-date: 2013-10-29 02:00 P.M UTC
+:release-by: Ask Solem
+
+- Message.channel is now a channel object (not the channel id).
+
+- Bug in previous version caused the socket to be flagged as disconnected
+ at EAGAIN/EINTR.
+
+.. _version-1.3.1:
+
+1.3.1
+=====
+:release-date: 2013-10-24 04:00 P.M UTC
+:release-by: Ask Solem
+
+- Now implements Connection.connected (Issue #22).
+
+- Fixed bug where ``str(AMQPError)`` did not return string.
+
+.. _version-1.3.0:
+
+1.3.0
+=====
+:release-date: 2013-09-04 02:39 P.M UTC
+:release-by: Ask Solem
+
+- Now sets ``Message.channel`` on delivery (Issue #12)
+
+ amqplib used to make the channel object available
+ as ``Message.delivery_info['channel']``, but this was removed
+ in py-amqp. librabbitmq sets ``Message.channel``,
+ which is a more reasonable solution in our opinion as that
+ keeps the delivery info intact.
+
+- New option to wait for publish confirmations (Issue #3)
+
+ There is now a new Connection ``confirm_publish`` that will
+ force any ``basic_publish`` call to wait for confirmation.
+
+ Enabling publisher confirms like this degrades performance
+ considerably, but can be suitable for some applications
+ and now it's possible by configuration.
+
+- ``queue_declare`` now returns named tuple of type
+ :class:`~amqp.protocol.basic_declare_ok_t`.
+
+ Supporting fields: ``queue``, ``message_count``, and
+ ``consumer_count``.
+
+- Contents of ``Channel.returned_messages`` is now named tuples.
+
+ Supporting fields: ``reply_code``, ``reply_text``, ``exchange``,
+ ``routing_key``, and ``message``.
+
+- Sockets now set to close on exec using the ``FD_CLOEXEC`` flag.
+
+ Currently only supported on platforms supporting this flag,
+ which does not include Windows.
+
+ Contributed by Tommie Gannert.
+
+.. _version-1.2.1:
+
+1.2.1
+=====
+:release-date: 2013-08-16 05:30 P.M UTC
+:release-by: Ask Solem
+
+- Adds promise type: :meth:`amqp.utils.promise`
+
+- Merges fixes from 1.0.x
+
+.. _version-1.2.0:
+
+1.2.0
+=====
+:release-date: 2012-11-12 04:00 P.M UTC
+:release-by: Ask Solem
+
+- New exception hierarchy:
+
+ - :class:`~amqp.AMQPError`
+ - :class:`~amqp.ConnectionError`
+ - :class:`~amqp.RecoverableConnectionError`
+ - :class:`~amqp.ConsumerCancelled`
+ - :class:`~amqp.ConnectionForced`
+ - :class:`~amqp.ResourceError`
+ - :class:`~IrrecoverableConnectionError`
+ - :class:`~amqp.ChannelNotOpen`
+ - :class:`~amqp.FrameError`
+ - :class:`~amqp.FrameSyntaxError`
+ - :class:`~amqp.InvalidCommand`
+ - :class:`~amqp.InvalidPath`
+ - :class:`~amqp.NotAllowed`
+ - :class:`~amqp.UnexpectedFrame`
+ - :class:`~amqp.AMQPNotImplementedError`
+ - :class:`~amqp.InternalError`
+ - :class:`~amqp.ChannelError`
+ - :class:`~RecoverableChannelError`
+ - :class:`~amqp.ContentTooLarge`
+ - :class:`~amqp.NoConsumers`
+ - :class:`~amqp.ResourceLocked`
+ - :class:`~IrrecoverableChannelError`
+ - :class:`~amqp.AccessRefused`
+ - :class:`~amqp.NotFound`
+ - :class:`~amqp.PreconditionFailed`
+
+
+.. _version-1.1.0:
+
+1.1.0
+=====
+:release-date: 2012-11-08 10:36 P.M UTC
+:release-by: Ask Solem
+
+- No longer supports Python 2.5
+
+- Fixed receiving of float table values.
+
+- Now Supports Python 3 and Python 2.6+ in the same source code.
+
+- Python 3 related fixes.
+
+.. _version-1.0.13:
+
+1.0.13
+======
+:release-date: 2013-07-31 04:00 P.M BST
+:release-by: Ask Solem
+
+- Fixed problems with the SSL transport (Issue #15).
+
+ Fix contributed by Adrien Guinet.
+
+- Small optimizations
+
+.. _version-1.0.12:
+
+1.0.12
+======
+:release-date: 2013-06-25 02:00 P.M BST
+:release-by: Ask Solem
+
+- Fixed another Python 3 compatibility problem.
+
+.. _version-1.0.11:
+
+1.0.11
+======
+:release-date: 2013-04-11 06:00 P.M BST
+:release-by: Ask Solem
+
+- Fixed Python 3 incompatibility in ``amqp/transport.py``.
+
+.. _version-1.0.10:
+
+1.0.10
+======
+:release-date: 2013-03-21 03:30 P.M UTC
+:release-by: Ask Solem
+
+- Fixed Python 3 incompatibility in ``amqp/serialization.py``.
+ (Issue #11).
+
+.. _version-1.0.9:
+
+1.0.9
+=====
+:release-date: 2013-03-08 10:40 A.M UTC
+:release-by: Ask Solem
+
+- Publisher ack callbacks should now work after typo fix (Issue #9).
+
+- ``channel(explicit_id)`` will now claim that id from the array
+ of unused channel ids.
+
+- Fixes Jython compatibility.
+
+.. _version-1.0.8:
+
+1.0.8
+=====
+:release-date: 2013-02-08 01:00 P.M UTC
+:release-by: Ask Solem
+
+- Fixed SyntaxError on Python 2.5
+
+.. _version-1.0.7:
+
+1.0.7
+=====
+:release-date: 2013-02-08 01:00 P.M UTC
+:release-by: Ask Solem
+
+- Workaround for bug on some Python 2.5 installations where (2**32) is 0.
+
+- Can now serialize the ARRAY type.
+
+ Contributed by Adam Wentz.
+
+- Fixed tuple format bug in exception (Issue #4).
+
+.. _version-1.0.6:
+
+1.0.6
+=====
+:release-date: 2012-11-29 01:14 P.M UTC
+:release-by: Ask Solem
+
+- ``Channel.close`` is now ignored if the connection attribute is None.
+
+.. _version-1.0.5:
+
+1.0.5
+=====
+:release-date: 2012-11-21 04:00 P.M UTC
+:release-by: Ask Solem
+
+- ``Channel.basic_cancel`` is now ignored if the channel was already closed.
+
+- ``Channel.events`` is now a dict of sets::
+
+ >>> channel.events['basic_return'].add(on_basic_return)
+ >>> channel.events['basic_return'].discard(on_basic_return)
+
+.. _version-1.0.4:
+
+1.0.4
+=====
+:release-date: 2012-11-13 04:00 P.M UTC
+:release-by: Ask Solem
+
+- Fixes Python 2.5 support
+
+.. _version-1.0.3:
+
+1.0.3
+=====
+:release-date: 2012-11-12 04:00 P.M UTC
+:release-by: Ask Solem
+
+- Now can also handle float in headers/tables when receiving messages.
+
+- Now uses :class:`array.array` to keep track of unused channel ids.
+
+- The :data:`~amqp.exceptions.METHOD_NAME_MAP` has been updated for
+ amqp/0.9.1 and Rabbit extensions.
+
+- Removed a bunch of accidentally included images.
+
+.. _version-1.0.2:
+
+1.0.2
+=====
+:release-date: 2012-11-06 05:00 P.M UTC
+:release-by: Ask Solem
+
+- Now supports float values in headers/tables.
+
+.. _version-1.0.1:
+
+1.0.1
+=====
+:release-date: 2012-11-05 01:00 P.M UTC
+:release-by: Ask Solem
+
+- Connection errors no longer includes :exc:`AttributeError`.
+
+- Fixed problem with using the SSL transport in a non-blocking context.
+
+ Fix contributed by Mher Movsisyan.
+
+
+.. _version-1.0.0:
+
+1.0.0
+=====
+:release-date: 2012-11-05 01:00 P.M UTC
+:release-by: Ask Solem
+
+- Channels are now restored on channel error, so that the connection does not
+ have to closed.
+
+.. _version-0.9.4:
+
+Version 0.9.4
+=============
+
+- Adds support for ``exchange_bind`` and ``exchange_unbind``.
+
+ Contributed by Rumyana Neykova
+
+- Fixed bugs in funtests and demo scripts.
+
+ Contributed by Rumyana Neykova
+
+.. _version-0.9.3:
+
+Version 0.9.3
+=============
+
+- Fixed bug that could cause the consumer to crash when reading
+ large message payloads asynchronously.
+
+- Serialization error messages now include the invalid value.
+
+.. _version-0.9.2:
+
+Version 0.9.2
+=============
+
+- Consumer cancel notification support was broken (Issue #1)
+
+ Fix contributed by Andrew Grangaard
+
+.. _version-0.9.1:
+
+Version 0.9.1
+=============
+
+- Supports draining events from multiple channels (``Connection.drain_events``)
+- Support for timeouts
+- Support for heartbeats
+ - ``Connection.heartbeat_tick(rate=2)`` must called at regular intervals
+ (half of the heartbeat value if rate is 2).
+ - Or some other scheme by using ``Connection.send_heartbeat``.
+- Supports RabbitMQ extensions:
+ - Consumer Cancel Notifications
+ - by default a cancel results in ``ChannelError`` being raised
+ - but not if a ``on_cancel`` callback is passed to ``basic_consume``.
+ - Publisher confirms
+ - ``Channel.confirm_select()`` enables publisher confirms.
+ - ``Channel.events['basic_ack'].append(my_callback)`` adds a callback
+ to be called when a message is confirmed. This callback is then
+ called with the signature ``(delivery_tag, multiple)``.
+- Support for ``basic_return``
+- Uses AMQP 0-9-1 instead of 0-8.
+ - ``Channel.access_request`` and ``ticket`` arguments to methods
+ **removed**.
+ - Supports the ``arguments`` argument to ``basic_consume``.
+ - ``internal`` argument to ``exchange_declare`` removed.
+ - ``auto_delete`` argument to ``exchange_declare`` deprecated
+ - ``insist`` argument to ``Connection`` removed.
+ - ``Channel.alerts`` has been removed.
+ - Support for ``Channel.basic_recover_async``.
+ - ``Channel.basic_recover`` deprecated.
+- Exceptions renamed to have idiomatic names:
+ - ``AMQPException`` -> ``AMQPError``
+ - ``AMQPConnectionException`` -> ConnectionError``
+ - ``AMQPChannelException`` -> ChannelError``
+ - ``Connection.known_hosts`` removed.
+ - ``Connection`` no longer supports redirects.
+ - ``exchange`` argument to ``queue_bind`` can now be empty
+ to use the "default exchange".
+- Adds ``Connection.is_alive`` that tries to detect
+ whether the connection can still be used.
+- Adds ``Connection.connection_errors`` and ``.channel_errors``,
+ a list of recoverable errors.
+- Exposes the underlying socket as ``Connection.sock``.
+- Adds ``Channel.no_ack_consumers`` to keep track of consumer tags
+ that set the no_ack flag.
+- Slightly better at error recovery
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..1191395
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,127 @@
+# -*- coding: utf-8 -*-
+
+import sys
+import os
+
+this = os.path.dirname(os.path.abspath(__file__))
+
+# If your extensions are in another directory, add it here. If the directory
+# is relative to the documentation root, use os.path.abspath to make it
+# absolute, like shown here.
+sys.path.append(os.path.join(os.pardir, "tests"))
+sys.path.append(os.path.join(this, "_ext"))
+import amqp
+
+# General configuration
+# ---------------------
+
+extensions = ['sphinx.ext.autodoc',
+ 'sphinx.ext.coverage',
+ 'sphinx.ext.pngmath',
+ 'sphinx.ext.intersphinx',
+ 'sphinxcontrib.issuetracker']
+
+html_show_sphinx = False
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['.templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'py-amqp'
+copyright = u'2009-2012, Ask Solem & Contributors'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = ".".join(map(str, amqp.VERSION[0:2]))
+# The full version, including alpha/beta/rc tags.
+release = amqp.__version__
+
+exclude_trees = ['.build']
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+add_function_parentheses = True
+
+intersphinx_mapping = {
+}
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'trac'
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['.static']
+
+html_use_smartypants = True
+
+# If false, no module index is generated.
+html_use_modindex = True
+
+# If false, no index is generated.
+html_use_index = True
+
+latex_documents = [
+ ('index', 'py-amqp.tex', ur'py-amqp Documentation',
+ ur'Ask Solem & Contributors', 'manual'),
+]
+
+html_theme = "celery"
+html_theme_path = ["_theme"]
+html_sidebars = {
+ 'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'],
+ '**': ['sidebarlogo.html', 'relations.html',
+ 'sourcelink.html', 'searchbox.html'],
+}
+
+### Issuetracker
+
+if False:
+ issuetracker = "github"
+ issuetracker_project = "celery/py-amqp"
+ issuetracker_issue_pattern = r'[Ii]ssue #(\d+)'
+
+# -- Options for Epub output ------------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = 'py-amqp Manual, Version 1.0'
+epub_author = 'Ask Solem'
+epub_publisher = 'Celery Project'
+epub_copyright = '2009-2012'
+
+# The language of the text. It defaults to the language option
+# or en if the language is not set.
+epub_language = 'en'
+
+# The scheme of the identifier. Typical schemes are ISBN or URL.
+epub_scheme = 'ISBN'
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+epub_identifier = 'celeryproject.org'
+
+# A unique identification for the text.
+epub_uid = 'py-amqp Manual, Version 1.0'
+
+# HTML files that should be inserted before the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+#epub_pre_files = []
+
+# HTML files shat should be inserted after the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+#epub_post_files = []
+
+# A list of files that should not be packed into the epub file.
+epub_exclude_files = ['search.html']
+
+
+# The depth of the table of contents in toc.ncx.
+epub_tocdepth = 3
diff --git a/docs/includes/intro.txt b/docs/includes/intro.txt
new file mode 100644
index 0000000..cc57ce1
--- /dev/null
+++ b/docs/includes/intro.txt
@@ -0,0 +1,96 @@
+:Version: 1.4.2
+:Web: http://amqp.readthedocs.org/
+:Download: http://pypi.python.org/pypi/amqp/
+:Source: http://github.com/celery/py-amqp/
+:Keywords: amqp, rabbitmq
+
+About
+=====
+
+This is a fork of amqplib_ which was originally written by Barry Pederson.
+It is maintained by the Celery_ project, and used by `kombu`_ as a pure python
+alternative when `librabbitmq`_ is not available.
+
+This library should be API compatible with `librabbitmq`_.
+
+.. _amqplib: http://pypi.python.org/pypi/amqplib
+.. _Celery: http://celeryproject.org/
+.. _kombu: http://kombu.readthedocs.org/
+.. _librabbitmq: http://pypi.python.org/pypi/librabbitmq
+
+Differences from `amqplib`_
+===========================
+
+- Supports draining events from multiple channels (``Connection.drain_events``)
+- Support for timeouts
+- Channels are restored after channel error, instead of having to close the
+ connection.
+- Support for heartbeats
+
+ - ``Connection.heartbeat_tick(rate=2)`` must called at regular intervals
+ (half of the heartbeat value if rate is 2).
+ - Or some other scheme by using ``Connection.send_heartbeat``.
+- Supports RabbitMQ extensions:
+ - Consumer Cancel Notifications
+ - by default a cancel results in ``ChannelError`` being raised
+ - but not if a ``on_cancel`` callback is passed to ``basic_consume``.
+ - Publisher confirms
+ - ``Channel.confirm_select()`` enables publisher confirms.
+ - ``Channel.events['basic_ack'].append(my_callback)`` adds a callback
+ to be called when a message is confirmed. This callback is then
+ called with the signature ``(delivery_tag, multiple)``.
+ - Exchange-to-exchange bindings: ``exchange_bind`` / ``exchange_unbind``.
+ - ``Channel.confirm_select()`` enables publisher confirms.
+ - ``Channel.events['basic_ack'].append(my_callback)`` adds a callback
+ to be called when a message is confirmed. This callback is then
+ called with the signature ``(delivery_tag, multiple)``.
+- Support for ``basic_return``
+- Uses AMQP 0-9-1 instead of 0-8.
+ - ``Channel.access_request`` and ``ticket`` arguments to methods
+ **removed**.
+ - Supports the ``arguments`` argument to ``basic_consume``.
+ - ``internal`` argument to ``exchange_declare`` removed.
+ - ``auto_delete`` argument to ``exchange_declare`` deprecated
+ - ``insist`` argument to ``Connection`` removed.
+ - ``Channel.alerts`` has been removed.
+ - Support for ``Channel.basic_recover_async``.
+ - ``Channel.basic_recover`` deprecated.
+- Exceptions renamed to have idiomatic names:
+ - ``AMQPException`` -> ``AMQPError``
+ - ``AMQPConnectionException`` -> ConnectionError``
+ - ``AMQPChannelException`` -> ChannelError``
+ - ``Connection.known_hosts`` removed.
+ - ``Connection`` no longer supports redirects.
+ - ``exchange`` argument to ``queue_bind`` can now be empty
+ to use the "default exchange".
+- Adds ``Connection.is_alive`` that tries to detect
+ whether the connection can still be used.
+- Adds ``Connection.connection_errors`` and ``.channel_errors``,
+ a list of recoverable errors.
+- Exposes the underlying socket as ``Connection.sock``.
+- Adds ``Channel.no_ack_consumers`` to keep track of consumer tags
+ that set the no_ack flag.
+- Slightly better at error recovery
+
+Further
+=======
+
+- Differences between AMQP 0.8 and 0.9.1
+
+ http://www.rabbitmq.com/amqp-0-8-to-0-9-1.html
+
+- AMQP 0.9.1 Quick Reference
+
+ http://www.rabbitmq.com/amqp-0-9-1-quickref.html
+
+- RabbitMQ Extensions
+
+ http://www.rabbitmq.com/extensions.html
+
+- For more information about AMQP, visit
+
+ http://www.amqp.org
+
+- For other Python client libraries see:
+
+ http://www.rabbitmq.com/devtools.html#python-dev
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..46c8ddd
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,22 @@
+=============================================
+ amqp - Python AMQP low-level client library
+=============================================
+
+.. include:: includes/intro.txt
+
+Contents
+========
+
+.. toctree::
+ :maxdepth: 2
+
+ reference/index
+ changelog
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
diff --git a/docs/reference/amqp.abstract_channel.rst b/docs/reference/amqp.abstract_channel.rst
new file mode 100644
index 0000000..022a4f2
--- /dev/null
+++ b/docs/reference/amqp.abstract_channel.rst
@@ -0,0 +1,11 @@
+=====================================================
+ amqp.abstract_channel
+=====================================================
+
+.. contents::
+ :local:
+.. currentmodule:: amqp.abstract_channel
+
+.. automodule:: amqp.abstract_channel
+ :members:
+ :undoc-members:
diff --git a/docs/reference/amqp.basic_message.rst b/docs/reference/amqp.basic_message.rst
new file mode 100644
index 0000000..43ee7f7
--- /dev/null
+++ b/docs/reference/amqp.basic_message.rst
@@ -0,0 +1,11 @@
+=====================================================
+ amqp.basic_message
+=====================================================
+
+.. contents::
+ :local:
+.. currentmodule:: amqp.basic_message
+
+.. automodule:: amqp.basic_message
+ :members:
+ :undoc-members:
diff --git a/docs/reference/amqp.channel.rst b/docs/reference/amqp.channel.rst
new file mode 100644
index 0000000..ff4b471
--- /dev/null
+++ b/docs/reference/amqp.channel.rst
@@ -0,0 +1,11 @@
+=====================================================
+ amqp.channel
+=====================================================
+
+.. contents::
+ :local:
+.. currentmodule:: amqp.channel
+
+.. automodule:: amqp.channel
+ :members:
+ :undoc-members:
diff --git a/docs/reference/amqp.connection.rst b/docs/reference/amqp.connection.rst
new file mode 100644
index 0000000..4a2e68c
--- /dev/null
+++ b/docs/reference/amqp.connection.rst
@@ -0,0 +1,11 @@
+=====================================================
+ amqp.connection
+=====================================================
+
+.. contents::
+ :local:
+.. currentmodule:: amqp.connection
+
+.. automodule:: amqp.connection
+ :members:
+ :undoc-members:
diff --git a/docs/reference/amqp.exceptions.rst b/docs/reference/amqp.exceptions.rst
new file mode 100644
index 0000000..5975c26
--- /dev/null
+++ b/docs/reference/amqp.exceptions.rst
@@ -0,0 +1,11 @@
+=====================================================
+ amqp.exceptions
+=====================================================
+
+.. contents::
+ :local:
+.. currentmodule:: amqp.exceptions
+
+.. automodule:: amqp.exceptions
+ :members:
+ :undoc-members:
diff --git a/docs/reference/amqp.five.rst b/docs/reference/amqp.five.rst
new file mode 100644
index 0000000..b4dc083
--- /dev/null
+++ b/docs/reference/amqp.five.rst
@@ -0,0 +1,11 @@
+=====================================================
+ amqp.five
+=====================================================
+
+.. contents::
+ :local:
+.. currentmodule:: amqp.five
+
+.. automodule:: amqp.five
+ :members:
+ :undoc-members:
diff --git a/docs/reference/amqp.method_framing.rst b/docs/reference/amqp.method_framing.rst
new file mode 100644
index 0000000..8c45982
--- /dev/null
+++ b/docs/reference/amqp.method_framing.rst
@@ -0,0 +1,11 @@
+=====================================================
+ amqp.method_framing
+=====================================================
+
+.. contents::
+ :local:
+.. currentmodule:: amqp.method_framing
+
+.. automodule:: amqp.method_framing
+ :members:
+ :undoc-members:
diff --git a/docs/reference/amqp.protocol.rst b/docs/reference/amqp.protocol.rst
new file mode 100644
index 0000000..e47a469
--- /dev/null
+++ b/docs/reference/amqp.protocol.rst
@@ -0,0 +1,11 @@
+=====================================================
+ amqp.protocol
+=====================================================
+
+.. contents::
+ :local:
+.. currentmodule:: amqp.protocol
+
+.. automodule:: amqp.protocol
+ :members:
+ :undoc-members:
diff --git a/docs/reference/amqp.serialization.rst b/docs/reference/amqp.serialization.rst
new file mode 100644
index 0000000..858e297
--- /dev/null
+++ b/docs/reference/amqp.serialization.rst
@@ -0,0 +1,11 @@
+=====================================================
+ amqp.serialization
+=====================================================
+
+.. contents::
+ :local:
+.. currentmodule:: amqp.serialization
+
+.. automodule:: amqp.serialization
+ :members:
+ :undoc-members:
diff --git a/docs/reference/amqp.transport.rst b/docs/reference/amqp.transport.rst
new file mode 100644
index 0000000..fae34b1
--- /dev/null
+++ b/docs/reference/amqp.transport.rst
@@ -0,0 +1,11 @@
+=====================================================
+ amqp.transport
+=====================================================
+
+.. contents::
+ :local:
+.. currentmodule:: amqp.transport
+
+.. automodule:: amqp.transport
+ :members:
+ :undoc-members:
diff --git a/docs/reference/amqp.utils.rst b/docs/reference/amqp.utils.rst
new file mode 100644
index 0000000..1d590ae
--- /dev/null
+++ b/docs/reference/amqp.utils.rst
@@ -0,0 +1,11 @@
+=====================================================
+ amqp.utils
+=====================================================
+
+.. contents::
+ :local:
+.. currentmodule:: amqp.utils
+
+.. automodule:: amqp.utils
+ :members:
+ :undoc-members:
diff --git a/docs/reference/index.rst b/docs/reference/index.rst
new file mode 100644
index 0000000..3a19812
--- /dev/null
+++ b/docs/reference/index.rst
@@ -0,0 +1,23 @@
+.. _apiref:
+
+===============
+ API Reference
+===============
+
+:Release: |version|
+:Date: |today|
+
+.. toctree::
+ :maxdepth: 1
+
+ amqp.connection
+ amqp.channel
+ amqp.basic_message
+ amqp.exceptions
+ amqp.abstract_channel
+ amqp.transport
+ amqp.method_framing
+ amqp.protocol
+ amqp.serialization
+ amqp.utils
+ amqp.five
diff --git a/docs/templates/readme.txt b/docs/templates/readme.txt
new file mode 100644
index 0000000..315cc1c
--- /dev/null
+++ b/docs/templates/readme.txt
@@ -0,0 +1,5 @@
+=====================================================================
+ Python AMQP 0.9.1 client library
+=====================================================================
+
+.. include:: ../includes/intro.txt
diff --git a/extra/README b/extra/README
new file mode 100644
index 0000000..6cc994c
--- /dev/null
+++ b/extra/README
@@ -0,0 +1,10 @@
+generate_skeleton_0_8.py was used to create an initial Python
+module from the AMQP 0.8 spec file.
+
+The 0-8 spec file is available from:
+
+ https://svn.amqp.org/amqp/tags/amqp_spec_0.8/amqp.xml
+
+A skeleton module named 'myskeleton.py' is generated by running
+
+ generate_skeleton_0_8.py amqp.xml myskeleton.py
diff --git a/extra/generate_skeleton_0_8.py b/extra/generate_skeleton_0_8.py
new file mode 100755
index 0000000..3bf4482
--- /dev/null
+++ b/extra/generate_skeleton_0_8.py
@@ -0,0 +1,377 @@
+#!/usr/bin/env python
+"""
+Utility for parsing an AMQP XML spec file
+and generating a Python module skeleton.
+
+This is a fairly ugly program, but it's only intended
+to be run once.
+
+2007-11-10 Barry Pederson <bp@barryp.org>
+
+"""
+# Copyright (C) 2007 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301 USA
+
+import sys
+import textwrap
+
+from xml.etree import ElementTree
+
+
+#########
+#
+# Helper code inspired by
+# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/498286
+# described in http://www.agapow.net/programming/python/the-etree-tail-quirk
+#
+def _textlist(self, _addtail=False):
+ '''Returns a list of text strings contained within
+ an element and its sub-elements.
+
+ Helpful for extracting text from prose-oriented XML
+ (such as XHTML or DocBook).
+ '''
+ result = []
+ if (not _addtail) and (self.text is not None):
+ result.append(self.text)
+ for elem in self:
+ result.extend(elem.textlist(True))
+ if _addtail and self.tail is not None:
+ result.append(self.tail)
+ return result
+
+# inject the new method into the ElementTree framework
+ElementTree._Element.textlist = _textlist
+
+#
+#
+#########
+
+domains = {}
+method_name_map = {}
+
+
+def _fixup_method_name(class_element, method_element):
+ if class_element.attrib['name'] != class_element.attrib['handler']:
+ prefix = '%s_' % class_element.attrib['name']
+ else:
+ prefix = ''
+ return ('%s%s' % (prefix, method_element.attrib['name'])).replace('-', '_')
+
+
+def _fixup_field_name(field_element):
+ result = field_element.attrib['name'].replace(' ', '_')
+ if result == 'global':
+ result = 'a_global'
+ return result
+
+
+def _field_type(field_element):
+ if 'type' in field_element.attrib:
+ return field_element.attrib['type']
+ if 'domain' in field_element.attrib:
+ return domains[field_element.attrib['domain']]
+
+
+def _reindent(s, indent, reformat=True):
+ """
+ Remove the existing indentation from each line of a chunk of
+ text, s, and then prefix each line with a new indent string.
+
+ Also removes trailing whitespace from each line, and leading and
+ trailing blank lines.
+
+ """
+ s = textwrap.dedent(s)
+ s = s.split('\n')
+ s = [x.rstrip() for x in s]
+ while s and (not s[0]):
+ s = s[1:]
+ while s and (not s[-1]):
+ s = s[:-1]
+ if reformat:
+ s = '\n'.join(s)
+ s = textwrap.wrap(s, initial_indent=indent, subsequent_indent=indent)
+ else:
+ s = [indent + x for x in s]
+ return '\n'.join(s) + '\n'
+
+
+def generate_docstr(element, indent='', wrap=None):
+ print 'Generate objects'
+ """
+ Generate a Python docstr for a given element in the AMQP
+ XML spec file. The element could be a class or method
+
+ The 'wrap' parameter is an optional chunk of text that's
+ added to the beginning and end of the resulting docstring.
+
+ """
+ result = []
+
+ txt = element.text and element.text.rstrip()
+ if txt:
+ result.append(_reindent(txt, indent))
+ result.append(indent)
+ extra_indent = ''
+ """
+ rules = element.findall('rule')
+ if rules:
+ result.append(indent + 'RULES:')
+ for r in rules:
+ result.append(indent + 'RULE:')
+ result.append(indent)
+ extra_indent = ' '
+ rule_docs = generate_docstr(r, indent + ' ')
+ if rule_docs:
+ result.append(extra_indent)
+ result.append(rule_docs)
+ result.append(indent)
+ """
+ for d in element.findall('doc') + element.findall('rule'):
+ if d.tag == 'rule':
+ result.append(indent + 'RULE:')
+ result.append(indent)
+ extra_indent = ' '
+ d = d.findall('doc')[0]
+
+ docval = ''.join(d.textlist()).rstrip()
+ if not docval:
+ continue
+ reformat = True
+ if 'name' in d.attrib:
+ result.append(indent + d.attrib['name'].upper() + ':')
+ result.append(indent)
+ extra_indent = ' '
+ if d.attrib['name'] == 'grammar':
+ reformat = False # Don't want re-indenting to mess this up
+ #else:
+ # extra_indent = ''
+ result.append(_reindent(docval, indent + extra_indent, reformat))
+ result.append(indent)
+
+ fields = element.findall('field')
+ if fields:
+ result.append(indent + 'PARAMETERS:')
+ for f in fields:
+ result.append(indent + ' ' + _fixup_field_name(f) +
+ ': ' + _field_type(f))
+ field_docs = generate_docstr(f, indent + ' ')
+ if field_docs:
+ result.append(indent)
+ result.append(field_docs)
+ result.append(indent)
+
+ if not result:
+ return None
+
+ if wrap is not None:
+ result = [wrap] + result + [wrap]
+
+ return '\n'.join(x.rstrip() for x in result) + '\n'
+
+
+def generate_methods(class_element, out):
+ methods = class_element.findall('method')
+ methods.sort(key=lambda x: x.attrib['name'])
+
+ for amqp_method in methods:
+ fields = amqp_method.findall('field')
+ fieldnames = [_fixup_field_name(x) for x in fields]
+
+ # move any 'ticket' arguments to the end of the method declaration
+ # so that they can have a default value.
+ if 'ticket' in fieldnames:
+ fieldnames = [x for x in fieldnames if x != 'ticket'] + ['ticket']
+
+ chassis = [x.attrib['name'] for x in amqp_method.findall('chassis')]
+ if 'server' in chassis:
+ params = ['self']
+ if 'content' in amqp_method.attrib:
+ params.append('msg')
+
+ out.write(' def %s(%s):\n' % (
+ _fixup_method_name(class_element, amqp_method),
+ ', '.join(params + fieldnames)),
+ )
+
+ s = generate_docstr(amqp_method, ' ', ' """')
+ if s:
+ out.write(s)
+
+ if fields:
+ out.write(' args = AMQPWriter()\n')
+ smf_arg = ', args'
+ else:
+ smf_arg = ''
+ for f in fields:
+ out.write(' args.write_%s(%s)\n' % (
+ _field_type(f), _fixup_field_name(f)))
+
+ if class_element.attrib['name'] == 'connection':
+ smf_pattern = ' self.send_method_frame(0, (%s, %s)%s)\n'
+ else:
+ smf_pattern = ' self.send_method_frame((%s, %s)%s)\n'
+
+ out.write(smf_pattern % (class_element.attrib['index'],
+ amqp_method.attrib['index'], smf_arg))
+
+ if 'synchronous' in amqp_method.attrib:
+ responses = [x.attrib['name']
+ for x in amqp_method.findall('response')]
+ out.write(' return self.wait(allowed_methods=[\n')
+ for r in responses:
+ resp = method_name_map[(class_element.attrib['name'], r)]
+ out.write(
+ ' (%s, %s), # %s\n' % resp)
+ out.write(' ])\n')
+
+ out.write('\n\n')
+
+ if 'client' in chassis:
+ out.write(' def _%s(self, args):\n' % (
+ _fixup_method_name(class_element, amqp_method), ))
+ s = generate_docstr(amqp_method, ' ', ' """')
+ if s:
+ out.write(s)
+ need_pass = True
+ for f in fields:
+ out.write(' %s = args.read_%s()\n' % (
+ _fixup_field_name(f), _field_type(f)))
+ need_pass = False
+ if 'content' in amqp_method.attrib:
+ out.write(' msg = self.wait()\n')
+ need_pass = False
+ if need_pass:
+ out.write(' pass\n')
+ out.write('\n\n')
+
+
+def generate_class(spec, class_element, out):
+ out.write('class %s(object):\n' % (
+ class_element.attrib['name'].capitalize(), ))
+ s = generate_docstr(class_element, ' ', ' """')
+ if s:
+ out.write(s)
+
+ generate_methods(class_element, out)
+
+ #
+ # Generate methods for handled classes
+ #
+ for amqp_class in spec.findall('class'):
+ if (amqp_class.attrib['handler'] == class_element.attrib['name']) and \
+ (amqp_class.attrib['name'] != class_element.attrib['name']):
+ out.write(' #############\n')
+ out.write(' #\n')
+ out.write(' # %s\n' % amqp_class.attrib['name'].capitalize())
+ out.write(' #\n')
+ s = generate_docstr(amqp_class, ' # ', ' # ')
+ if s:
+ out.write(s)
+ out.write('\n')
+
+ generate_methods(amqp_class, out)
+
+
+def generate_module(spec, out):
+ """
+ Given an AMQP spec parsed into an xml.etree.ElemenTree,
+ and a file-like 'out' object to write to, generate
+ the skeleton of a Python module.
+
+ """
+ #
+ # HACK THE SPEC so that 'access' is handled by
+ # 'channel' instead of 'connection'
+ #
+ for amqp_class in spec.findall('class'):
+ if amqp_class.attrib['name'] == 'access':
+ amqp_class.attrib['handler'] = 'channel'
+
+ #
+ # Build up some helper dictionaries
+ #
+ for domain in spec.findall('domain'):
+ domains[domain.attrib['name']] = domain.attrib['type']
+
+ for amqp_class in spec.findall('class'):
+ for amqp_method in amqp_class.findall('method'):
+ method_name_map[(amqp_class.attrib['name'],
+ amqp_method.attrib['name'])] = \
+ (
+ amqp_class.attrib['index'],
+ amqp_method.attrib['index'],
+ (amqp_class.attrib['handler'].capitalize() + '.' +
+ _fixup_method_name(amqp_class, amqp_method)),
+ )
+
+ #### Actually generate output
+
+ for amqp_class in spec.findall('class'):
+ if amqp_class.attrib['handler'] == amqp_class.attrib['name']:
+ generate_class(spec, amqp_class, out)
+
+ out.write('_METHOD_MAP = {\n')
+ for amqp_class in spec.findall('class'):
+ print amqp_class.attrib
+# for chassis in amqp_class.findall('chassis'):
+# print ' ', chassis.attrib
+ for amqp_method in amqp_class.findall('method'):
+# print ' ', amqp_method.attrib
+# for chassis in amqp_method.findall('chassis'):
+# print ' ', chassis.attrib
+ chassis = [x.attrib['name']
+ for x in amqp_method.findall('chassis')]
+ if 'client' in chassis:
+ out.write(" (%s, %s): (%s, %s._%s),\n" % (
+ amqp_class.attrib['index'],
+ amqp_method.attrib['index'],
+ amqp_class.attrib['handler'].capitalize(),
+ amqp_class.attrib['handler'].capitalize(),
+ _fixup_method_name(amqp_class, amqp_method)))
+ out.write('}\n\n')
+
+ out.write('_METHOD_NAME_MAP = {\n')
+ for amqp_class in spec.findall('class'):
+ for amqp_method in amqp_class.findall('method'):
+ out.write(" (%s, %s): '%s.%s',\n" % (
+ amqp_class.attrib['index'],
+ amqp_method.attrib['index'],
+ amqp_class.attrib['handler'].capitalize(),
+ _fixup_method_name(amqp_class, amqp_method)))
+ out.write('}\n')
+
+
+def main(argv=None):
+ if argv is None:
+ argv = sys.argv
+
+ if len(argv) < 2:
+ print('Usage: %s <amqp-spec> [<output-file>]' % argv[0])
+ return 1
+
+ spec = ElementTree.parse(argv[1])
+ if len(argv) < 3:
+ out = sys.stdout
+ else:
+ out = open(argv[2], 'w')
+
+ generate_module(spec, out)
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/extra/release/bump_version.py b/extra/release/bump_version.py
new file mode 100755
index 0000000..87bb6bd
--- /dev/null
+++ b/extra/release/bump_version.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python
+
+from __future__ import absolute_import
+
+import errno
+import os
+import re
+import shlex
+import subprocess
+import sys
+
+from contextlib import contextmanager
+from tempfile import NamedTemporaryFile
+
+rq = lambda s: s.strip("\"'")
+
+
+def cmd(*args):
+ return subprocess.Popen(args, stdout=subprocess.PIPE).communicate()[0]
+
+
+@contextmanager
+def no_enoent():
+ try:
+ yield
+ except OSError as exc:
+ if exc.errno != errno.ENOENT:
+ raise
+
+
+class StringVersion(object):
+
+ def decode(self, s):
+ s = rq(s)
+ text = ""
+ major, minor, release = s.split(".")
+ if not release.isdigit():
+ pos = release.index(re.split("\d+", release)[1][0])
+ release, text = release[:pos], release[pos:]
+ return int(major), int(minor), int(release), text
+
+ def encode(self, v):
+ return ".".join(map(str, v[:3])) + v[3]
+to_str = StringVersion().encode
+from_str = StringVersion().decode
+
+
+class TupleVersion(object):
+
+ def decode(self, s):
+ v = list(map(rq, s.split(", ")))
+ return (tuple(map(int, v[0:3])) +
+ tuple(["".join(v[3:])]))
+
+ def encode(self, v):
+ v = list(v)
+
+ def quote(lit):
+ if isinstance(lit, basestring):
+ return '"{0}"'.format(lit)
+ return str(lit)
+
+ if not v[-1]:
+ v.pop()
+ return ", ".join(map(quote, v))
+
+
+class VersionFile(object):
+
+ def __init__(self, filename):
+ self.filename = filename
+ self._kept = None
+
+ def _as_orig(self, version):
+ return self.wb.format(version=self.type.encode(version),
+ kept=self._kept)
+
+ def write(self, version):
+ pattern = self.regex
+ with no_enoent():
+ with NamedTemporaryFile() as dest:
+ with open(self.filename) as orig:
+ for line in orig:
+ if pattern.match(line):
+ dest.write(self._as_orig(version))
+ else:
+ dest.write(line)
+ os.rename(dest.name, self.filename)
+
+ def parse(self):
+ pattern = self.regex
+ gpos = 0
+ with open(self.filename) as fh:
+ for line in fh:
+ m = pattern.match(line)
+ if m:
+ if "?P<keep>" in pattern.pattern:
+ self._kept, gpos = m.groupdict()["keep"], 1
+ return self.type.decode(m.groups()[gpos])
+
+
+class PyVersion(VersionFile):
+ regex = re.compile(r'^VERSION\s*=\s*\((.+?)\)')
+ wb = "VERSION = ({version})\n"
+ type = TupleVersion()
+
+
+class SphinxVersion(VersionFile):
+ regex = re.compile(r'^:[Vv]ersion:\s*(.+?)$')
+ wb = ':Version: {version}\n'
+ type = StringVersion()
+
+
+class CPPVersion(VersionFile):
+ regex = re.compile(r'^\#\s*define\s*(?P<keep>\w*)VERSION\s+(.+)')
+ wb = '#define {kept}VERSION "{version}"\n'
+ type = StringVersion()
+
+
+_filetype_to_type = {"py": PyVersion,
+ "rst": SphinxVersion,
+ "txt": SphinxVersion,
+ "c": CPPVersion,
+ "h": CPPVersion}
+
+
+def filetype_to_type(filename):
+ _, _, suffix = filename.rpartition(".")
+ return _filetype_to_type[suffix](filename)
+
+
+def bump(*files, **kwargs):
+ version = kwargs.get("version")
+ before_commit = kwargs.get("before_commit")
+ files = [filetype_to_type(f) for f in files]
+ versions = [v.parse() for v in files]
+ current = list(reversed(sorted(versions)))[0] # find highest
+ current = current.split()[0] # only first sentence
+
+ if version:
+ next = from_str(version)
+ else:
+ major, minor, release, text = current
+ if text:
+ raise Exception("Can't bump alpha releases")
+ next = (major, minor, release + 1, text)
+
+ print("Bump version from {0} -> {1}".format(to_str(current), to_str(next)))
+
+ for v in files:
+ print(" writing {0.filename!r}...".format(v))
+ v.write(next)
+
+ if before_commit:
+ cmd(*shlex.split(before_commit))
+
+ print(cmd("git", "commit", "-m", "Bumps version to {0}".format(
+ to_str(next)), *[f.filename for f in files]))
+ print(cmd("git", "tag", "v{0}".format(to_str(next))))
+
+
+def main(argv=sys.argv, version=None, before_commit=None):
+ if not len(argv) > 1:
+ print("Usage: distdir [docfile] -- <custom version>")
+ sys.exit(0)
+
+ args = []
+ for arg in argv:
+ if arg.startswith("--before-commit="):
+ _, before_commit = arg.split('=')
+ else:
+ args.append(arg)
+
+ if "--" in args:
+ c = args.index('--')
+ version = args[c + 1]
+ argv = args[:c]
+ bump(*args[1:], version=version, before_commit=before_commit)
+
+if __name__ == "__main__":
+ main()
diff --git a/extra/release/sphinx-to-rst.py b/extra/release/sphinx-to-rst.py
new file mode 100755
index 0000000..e0c1fd0
--- /dev/null
+++ b/extra/release/sphinx-to-rst.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+import os
+import re
+import sys
+
+dirname = ""
+
+RE_CODE_BLOCK = re.compile(r'.. code-block:: (.+?)\s*$')
+RE_INCLUDE = re.compile(r'.. include:: (.+?)\s*$')
+RE_REFERENCE = re.compile(r':(.+?):`(.+?)`')
+
+
+def include_file(lines, pos, match):
+ global dirname
+ orig_filename = match.groups()[0]
+ filename = os.path.join(dirname, orig_filename)
+ fh = open(filename)
+ try:
+ old_dirname = dirname
+ dirname = os.path.dirname(orig_filename)
+ try:
+ lines[pos] = sphinx_to_rst(fh)
+ finally:
+ dirname = old_dirname
+ finally:
+ fh.close()
+
+
+def replace_code_block(lines, pos, match):
+ lines[pos] = ""
+ curpos = pos - 1
+ # Find the first previous line with text to append "::" to it.
+ while True:
+ prev_line = lines[curpos]
+ if not prev_line.isspace():
+ prev_line_with_text = curpos
+ break
+ curpos -= 1
+
+ if lines[prev_line_with_text].endswith(":"):
+ lines[prev_line_with_text] += ":"
+ else:
+ lines[prev_line_with_text] += "::"
+
+TO_RST_MAP = {RE_CODE_BLOCK: replace_code_block,
+ RE_REFERENCE: r'``\2``',
+ RE_INCLUDE: include_file}
+
+
+def _process(lines):
+ lines = list(lines) # non-destructive
+ for i, line in enumerate(lines):
+ for regex, alt in TO_RST_MAP.items():
+ if callable(alt):
+ match = regex.match(line)
+ if match:
+ alt(lines, i, match)
+ line = lines[i]
+ else:
+ lines[i] = regex.sub(alt, line)
+ return lines
+
+
+def sphinx_to_rst(fh):
+ return "".join(_process(fh))
+
+
+if __name__ == "__main__":
+ global dirname
+ dirname = os.path.dirname(sys.argv[1])
+ fh = open(sys.argv[1])
+ try:
+ print(sphinx_to_rst(fh))
+ finally:
+ fh.close()
diff --git a/extra/update_comments_from_spec.py b/extra/update_comments_from_spec.py
new file mode 100644
index 0000000..677dd5c
--- /dev/null
+++ b/extra/update_comments_from_spec.py
@@ -0,0 +1,76 @@
+from __future__ import absolute_import
+
+import os
+import sys
+import re
+
+default_source_file = os.path.join(
+ os.path.dirname(__file__),
+ '../amqp/channel.py',
+)
+
+RE_COMMENTS = re.compile(
+ '(?P<methodsig>def\s+(?P<mname>[a-zA-Z0-9_]+)\(.*?\)'
+ ':\n+\s+""")(?P<comment>.*?)(?=""")',
+ re.MULTILINE | re.DOTALL
+)
+
+USAGE = """\
+Usage: %s <comments-file> <output-file> [<source-file>]\
+"""
+
+
+def update_comments(comments_file, impl_file, result_file):
+ text_file = open(impl_file, 'r')
+ source = text_file.read()
+
+ comments = get_comments(comments_file)
+ for def_name, comment in comments.items():
+ source = replace_comment_per_def(
+ source, result_file, def_name, comment
+ )
+
+ new_file = open(result_file, 'w+')
+ new_file.write(source)
+
+
+def get_comments(filename):
+ text_file = open(filename, 'r')
+ whole_source = text_file.read()
+ comments = {}
+
+ all_matches = RE_COMMENTS.finditer(whole_source)
+ for match in all_matches:
+ comments[match.group('mname')] = match.group('comment')
+ #print('method: %s \ncomment: %s' % (
+ # match.group('mname'), match.group('comment')))
+
+ return comments
+
+
+def replace_comment_per_def(source, result_file, def_name, new_comment):
+ regex = ('(?P<methodsig>def\s+' +
+ def_name +
+ '\(.*?\):\n+\s+""".*?\n).*?(?=""")')
+ #print('method and comment:' + def_name + new_comment)
+ result = re.sub(regex, '\g<methodsig>' + new_comment, source, 0,
+ re.MULTILINE | re.DOTALL)
+ return result
+
+
+def main(argv=None):
+ if argv is None:
+ argv = sys.argv
+
+ if len(argv) < 3:
+ print(USAGE % argv[0])
+ return 1
+
+ impl_file = default_source_file
+ if len(argv) >= 4:
+ impl_file = argv[3]
+
+ update_comments(argv[1], impl_file, argv[2])
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/funtests/run_all.py b/funtests/run_all.py
new file mode 100755
index 0000000..8d3fb40
--- /dev/null
+++ b/funtests/run_all.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+"""Run all the unittest modules for amqp"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+
+import unittest
+
+import settings
+
+TEST_NAMES = [
+ 'test_exceptions',
+ 'test_serialization',
+ 'test_basic_message',
+ 'test_connection',
+ 'test_channel',
+ 'test_with',
+]
+
+
+def main():
+ suite = unittest.TestLoader().loadTestsFromNames(TEST_NAMES)
+ unittest.TextTestRunner(**settings.test_args).run(suite)
+
+if __name__ == '__main__':
+ main()
diff --git a/funtests/settings.py b/funtests/settings.py
new file mode 100644
index 0000000..d935421
--- /dev/null
+++ b/funtests/settings.py
@@ -0,0 +1,91 @@
+"""Parse commandline args for running unittests.
+
+Used by the overall run_all.py script, or the various
+indivudial test modules that need settings for connecting
+to a broker.
+
+"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+
+import logging
+from optparse import OptionParser
+
+connect_args = {}
+test_args = {'verbosity': 1}
+
+
+def parse_args():
+ parser = OptionParser(usage='usage: %prog [options]')
+ parser.add_option(
+ '--host', dest='host',
+ help='AMQP server to connect to (default: %default)',
+ default='localhost',
+ )
+ parser.add_option(
+ '-u', '--userid', dest='userid',
+ help='userid to authenticate as (default: %default)',
+ default='guest',
+ )
+ parser.add_option(
+ '-p', '--password', dest='password',
+ help='password to authenticate with (default: %default)',
+ default='guest',
+ )
+ parser.add_option(
+ '--ssl', dest='ssl', action='store_true',
+ help='Enable SSL (default: not enabled)',
+ default=False,
+ )
+ parser.add_option(
+ '--debug', dest='debug', action='store_true',
+ help='Display debugging output',
+ default=False,
+ )
+ parser.add_option(
+ '--port', dest='port',
+ help='port for the broker',
+ default=5672,
+ )
+
+ parser.add_option(
+ '-v', '--verbosity', dest='verbose', action='store_true',
+ help='Run unittests with increased verbosity',
+ default=False,
+ )
+
+ options, args = parser.parse_args()
+
+ if options.debug:
+ console = logging.StreamHandler()
+ console.setLevel(logging.DEBUG)
+ formatter = logging.Formatter(
+ '%(name)-12s: %(levelname)-8s %(message)s',
+ )
+ console.setFormatter(formatter)
+ amqp_logger = logging.getLogger('amqp')
+ amqp_logger.addHandler(console)
+ amqp_logger.setLevel(logging.DEBUG)
+
+ connect_args['host'] = options.host
+ connect_args['userid'] = options.userid
+ connect_args['password'] = options.password
+ connect_args['ssl'] = options.ssl
+
+ if options.verbose:
+ test_args['verbosity'] = 2
+
+parse_args()
diff --git a/funtests/test_basic_message.py b/funtests/test_basic_message.py
new file mode 100755
index 0000000..268f360
--- /dev/null
+++ b/funtests/test_basic_message.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python
+"""
+Test the amqp.basic_message module.
+
+"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+
+from datetime import datetime
+from decimal import Decimal
+
+import unittest
+
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle # noqa
+
+
+import settings
+
+from amqp.basic_message import Message
+
+
+class TestBasicMessage(unittest.TestCase):
+
+ def check_proplist(self, msg):
+ """Check roundtrip processing of a single object"""
+ raw_properties = msg._serialize_properties()
+
+ new_msg = Message()
+ new_msg._load_properties(raw_properties)
+ new_msg.body = msg.body
+
+ self.assertEqual(msg, new_msg)
+
+ def test_eq(self):
+ msg = Message('hello', content_type='text/plain')
+ self.assertNotEqual(msg, None)
+
+ #
+ # Make sure that something that looks vaguely
+ # like a Message doesn't raise an Attribute
+ # error when compared to a Message, and instead
+ # returns False
+ #
+ class FakeMsg(object):
+ pass
+
+ fake_msg = FakeMsg()
+ fake_msg.properties = {'content_type': 'text/plain'}
+
+ self.assertNotEqual(msg, fake_msg)
+
+ def test_pickle(self):
+ msg = Message(
+ 'some body' * 200000,
+ content_type='text/plain',
+ content_encoding='utf-8',
+ application_headers={
+ 'foo': 7, 'bar': 'baz', 'd2': {'foo2': 'xxx', 'foo3': -1},
+ },
+ delivery_mode=1,
+ priority=7,
+ )
+
+ msg2 = pickle.loads(pickle.dumps(msg))
+ self.assertEqual(msg, msg2)
+
+ def test_roundtrip(self):
+ """Check round-trip processing of content-properties."""
+ self.check_proplist(Message())
+
+ self.check_proplist(Message(content_type='text/plain'))
+
+ self.check_proplist(Message(
+ content_type='text/plain',
+ content_encoding='utf-8',
+ application_headers={
+ 'foo': 7, 'bar': 'baz', 'd2': {'foo2': 'xxx', 'foo3': -1},
+ },
+ delivery_mode=1,
+ priority=7,
+ ))
+
+ self.check_proplist(Message(
+ application_headers={
+ 'regular': datetime(2007, 11, 12, 12, 34, 56),
+ 'dst': datetime(2007, 7, 12, 12, 34, 56),
+ },
+ ))
+
+ n = datetime.now()
+ # AMQP only does timestamps to 1-second resolution
+ n = n.replace(microsecond=0)
+ self.check_proplist(Message(
+ application_headers={'foo': n}),
+ )
+
+ self.check_proplist(Message(
+ application_headers={'foo': Decimal('10.1')}),
+ )
+
+ self.check_proplist(Message(
+ application_headers={'foo': Decimal('-1987654.193')}),
+ )
+
+ self.check_proplist(Message(
+ timestamp=datetime(1980, 1, 2, 3, 4, 6)),
+ )
+
+
+def main():
+ suite = unittest.TestLoader().loadTestsFromTestCase(TestBasicMessage)
+ unittest.TextTestRunner(**settings.test_args).run(suite)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/funtests/test_channel.py b/funtests/test_channel.py
new file mode 100755
index 0000000..10b860f
--- /dev/null
+++ b/funtests/test_channel.py
@@ -0,0 +1,317 @@
+#!/usr/bin/env python
+"""
+Test amqp.channel module
+
+"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+
+import sys
+import unittest
+
+import settings
+
+from amqp import ChannelError, Connection, Message, FrameSyntaxError
+
+
+class TestChannel(unittest.TestCase):
+
+ def setUp(self):
+ self.conn = Connection(**settings.connect_args)
+ self.ch = self.conn.channel()
+
+ def tearDown(self):
+ self.ch.close()
+ self.conn.close()
+
+ def test_defaults(self):
+ """Test how a queue defaults to being bound to an AMQP default
+ exchange, and how publishing defaults to the default exchange, and
+ basic_get defaults to getting from the most recently declared queue,
+ and queue_delete defaults to deleting the most recently declared
+ queue."""
+ msg = Message(
+ 'funtest message',
+ content_type='text/plain',
+ application_headers={'foo': 7, 'bar': 'baz'},
+ )
+
+ qname, _, _ = self.ch.queue_declare()
+ self.ch.basic_publish(msg, routing_key=qname)
+
+ msg2 = self.ch.basic_get(no_ack=True)
+ self.assertEqual(msg, msg2)
+
+ n = self.ch.queue_purge()
+ self.assertEqual(n, 0)
+
+ n = self.ch.queue_delete()
+ self.assertEqual(n, 0)
+
+ def test_encoding(self):
+ my_routing_key = 'funtest.test_queue'
+
+ qname, _, _ = self.ch.queue_declare()
+ self.ch.queue_bind(qname, 'amq.direct', routing_key=my_routing_key)
+
+ #
+ # No encoding, body passed through unchanged
+ #
+ msg = Message('hello world')
+ self.ch.basic_publish(msg, 'amq.direct', routing_key=my_routing_key)
+ msg2 = self.ch.basic_get(qname, no_ack=True)
+ if sys.version_info[0] < 3:
+ self.assertFalse(hasattr(msg2, 'content_encoding'))
+ self.assertTrue(isinstance(msg2.body, str))
+ self.assertEqual(msg2.body, 'hello world')
+
+ #
+ # Default UTF-8 encoding of unicode body, returned as unicode
+ #
+ msg = Message(u'hello world')
+ self.ch.basic_publish(msg, 'amq.direct', routing_key=my_routing_key)
+ msg2 = self.ch.basic_get(qname, no_ack=True)
+ self.assertEqual(msg2.content_encoding, 'UTF-8')
+ self.assertTrue(isinstance(msg2.body, unicode))
+ self.assertEqual(msg2.body, u'hello world')
+
+ #
+ # Explicit latin_1 encoding, still comes back as unicode
+ #
+ msg = Message(u'hello world', content_encoding='latin_1')
+ self.ch.basic_publish(msg, 'amq.direct', routing_key=my_routing_key)
+ msg2 = self.ch.basic_get(qname, no_ack=True)
+ self.assertEqual(msg2.content_encoding, 'latin_1')
+ self.assertTrue(isinstance(msg2.body, unicode))
+ self.assertEqual(msg2.body, u'hello world')
+
+ #
+ # Plain string with specified encoding comes back as unicode
+ #
+ msg = Message('hello w\xf6rld', content_encoding='latin_1')
+ self.ch.basic_publish(msg, 'amq.direct', routing_key=my_routing_key)
+ msg2 = self.ch.basic_get(qname, no_ack=True)
+ self.assertEqual(msg2.content_encoding, 'latin_1')
+ self.assertTrue(isinstance(msg2.body, unicode))
+ self.assertEqual(msg2.body, u'hello w\u00f6rld')
+
+ #
+ # Plain string (bytes in Python 3.x) with bogus encoding
+ #
+
+ # don't really care about latin_1, just want bytes
+ test_bytes = u'hello w\xd6rld'.encode('latin_1')
+ msg = Message(test_bytes, content_encoding='I made this up')
+ self.ch.basic_publish(msg, 'amq.direct', routing_key=my_routing_key)
+ msg2 = self.ch.basic_get(qname, no_ack=True)
+ self.assertEqual(msg2.content_encoding, 'I made this up')
+ self.assertTrue(isinstance(msg2.body, bytes))
+ self.assertEqual(msg2.body, test_bytes)
+
+ #
+ # Turn off auto_decode for remaining tests
+ #
+ self.ch.auto_decode = False
+
+ #
+ # Unicode body comes back as utf-8 encoded str
+ #
+ msg = Message(u'hello w\u00f6rld')
+ self.ch.basic_publish(msg, 'amq.direct', routing_key=my_routing_key)
+ msg2 = self.ch.basic_get(qname, no_ack=True)
+ self.assertEqual(msg2.content_encoding, 'UTF-8')
+ self.assertTrue(isinstance(msg2.body, bytes))
+ self.assertEqual(msg2.body, u'hello w\xc3\xb6rld'.encode('latin_1'))
+
+ #
+ # Plain string with specified encoding stays plain string
+ #
+ msg = Message('hello w\xf6rld', content_encoding='latin_1')
+ self.ch.basic_publish(msg, 'amq.direct', routing_key=my_routing_key)
+ msg2 = self.ch.basic_get(qname, no_ack=True)
+ self.assertEqual(msg2.content_encoding, 'latin_1')
+ self.assertTrue(isinstance(msg2.body, bytes))
+ self.assertEqual(msg2.body, u'hello w\xf6rld'.encode('latin_1'))
+
+ #
+ # Explicit latin_1 encoding, comes back as str
+ #
+ msg = Message(u'hello w\u00f6rld', content_encoding='latin_1')
+ self.ch.basic_publish(msg, 'amq.direct', routing_key=my_routing_key)
+ msg2 = self.ch.basic_get(qname, no_ack=True)
+ self.assertEqual(msg2.content_encoding, 'latin_1')
+ self.assertTrue(isinstance(msg2.body, bytes))
+ self.assertEqual(msg2.body, u'hello w\xf6rld'.encode('latin_1'))
+
+ def test_queue_delete_empty(self):
+ self.assertFalse(
+ self.ch.queue_delete('bogus_queue_that_does_not_exist')
+ )
+
+ def test_survives_channel_error(self):
+ with self.assertRaises(ChannelError):
+ self.ch.queue_declare('krjqheewq_bogus', passive=True)
+ self.ch.queue_declare('funtest_survive')
+ self.ch.queue_declare('funtest_survive', passive=True)
+ self.assertEqual(
+ 0, self.ch.queue_delete('funtest_survive'),
+ )
+
+ def test_invalid_header(self):
+ """
+ Test sending a message with an unserializable object in the header
+
+ http://code.google.com/p/py-amqplib/issues/detail?id=17
+
+ """
+ qname, _, _ = self.ch.queue_declare()
+
+ msg = Message(application_headers={'test': object()})
+
+ self.assertRaises(
+ FrameSyntaxError, self.ch.basic_publish, msg, routing_key=qname,
+ )
+
+ def test_large(self):
+ """
+ Test sending some extra large messages.
+
+ """
+ qname, _, _ = self.ch.queue_declare()
+
+ for multiplier in [100, 1000, 10000]:
+ msg = Message(
+ 'funtest message' * multiplier,
+ content_type='text/plain',
+ application_headers={'foo': 7, 'bar': 'baz'},
+ )
+
+ self.ch.basic_publish(msg, routing_key=qname)
+
+ msg2 = self.ch.basic_get(no_ack=True)
+ self.assertEqual(msg, msg2)
+
+ def test_publish(self):
+ self.ch.exchange_declare('funtest.fanout', 'fanout', auto_delete=True)
+
+ msg = Message(
+ 'funtest message',
+ content_type='text/plain',
+ application_headers={'foo': 7, 'bar': 'baz'},
+ )
+
+ self.ch.basic_publish(msg, 'funtest.fanout')
+
+ def test_queue(self):
+ my_routing_key = 'funtest.test_queue'
+ msg = Message(
+ 'funtest message',
+ content_type='text/plain',
+ application_headers={'foo': 7, 'bar': 'baz'},
+ )
+
+ qname, _, _ = self.ch.queue_declare()
+ self.ch.queue_bind(qname, 'amq.direct', routing_key=my_routing_key)
+
+ self.ch.basic_publish(msg, 'amq.direct', routing_key=my_routing_key)
+
+ msg2 = self.ch.basic_get(qname, no_ack=True)
+ self.assertEqual(msg, msg2)
+
+ def test_unbind(self):
+ my_routing_key = 'funtest.test_queue'
+
+ qname, _, _ = self.ch.queue_declare()
+ self.ch.queue_bind(qname, 'amq.direct', routing_key=my_routing_key)
+ self.ch.queue_unbind(qname, 'amq.direct', routing_key=my_routing_key)
+
+ def test_basic_return(self):
+ self.ch.exchange_declare('funtest.fanout', 'fanout', auto_delete=True)
+
+ msg = Message(
+ 'funtest message',
+ content_type='text/plain',
+ application_headers={'foo': 7, 'bar': 'baz'})
+
+ self.ch.basic_publish(msg, 'funtest.fanout')
+ self.ch.basic_publish(msg, 'funtest.fanout', mandatory=True)
+ self.ch.basic_publish(msg, 'funtest.fanout', mandatory=True)
+ self.ch.basic_publish(msg, 'funtest.fanout', mandatory=True)
+ self.ch.close()
+
+ #
+ # 3 of the 4 messages we sent should have been returned
+ #
+ self.assertEqual(self.ch.returned_messages.qsize(), 3)
+
+ def test_exchange_bind(self):
+ """Test exchange binding.
+ Network configuration is as follows (-> is forwards to :
+ source_exchange -> dest_exchange -> queue
+ The test checks that once the message is publish to the
+ destination exchange(funtest.topic_dest) it is delivered to the queue.
+ """
+
+ test_routing_key = 'unit_test__key'
+ dest_exchange = 'funtest.topic_dest_bind'
+ source_exchange = 'funtest.topic_source_bind'
+
+ self.ch.exchange_declare(dest_exchange, 'topic', auto_delete=True)
+ self.ch.exchange_declare(source_exchange, 'topic', auto_delete=True)
+
+ qname, _, _ = self.ch.queue_declare()
+ self.ch.exchange_bind(destination=dest_exchange,
+ source=source_exchange,
+ routing_key=test_routing_key)
+
+ self.ch.queue_bind(qname, dest_exchange,
+ routing_key=test_routing_key)
+
+ msg = Message('funtest message',
+ content_type='text/plain',
+ application_headers={'foo': 7, 'bar': 'baz'})
+
+ self.ch.basic_publish(msg, source_exchange,
+ routing_key=test_routing_key)
+
+ msg2 = self.ch.basic_get(qname, no_ack=True)
+ self.assertEqual(msg, msg2)
+
+ def test_exchange_unbind(self):
+ dest_exchange = 'funtest.topic_dest_unbind'
+ source_exchange = 'funtest.topic_source_unbind'
+ test_routing_key = 'unit_test__key'
+
+ self.ch.exchange_declare(dest_exchange,
+ 'topic', auto_delete=True)
+ self.ch.exchange_declare(source_exchange,
+ 'topic', auto_delete=True)
+
+ self.ch.exchange_bind(destination=dest_exchange,
+ source=source_exchange,
+ routing_key=test_routing_key)
+
+ self.ch.exchange_unbind(destination=dest_exchange,
+ source=source_exchange,
+ routing_key=test_routing_key)
+
+
+def main():
+ suite = unittest.TestLoader().loadTestsFromTestCase(TestChannel)
+ unittest.TextTestRunner(**settings.test_args).run(suite)
+
+if __name__ == '__main__':
+ main()
diff --git a/funtests/test_connection.py b/funtests/test_connection.py
new file mode 100755
index 0000000..31938be
--- /dev/null
+++ b/funtests/test_connection.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env python
+"""Test amqp.connection module"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+
+import gc
+import unittest
+
+import settings
+
+
+from amqp import Connection
+
+
+class TestConnection(unittest.TestCase):
+
+ def setUp(self):
+ self.conn = Connection(**settings.connect_args)
+
+ def tearDown(self):
+ if self.conn:
+ self.conn.close()
+
+ def test_channel(self):
+ ch = self.conn.channel(1)
+ self.assertEqual(ch.channel_id, 1)
+
+ ch2 = self.conn.channel()
+ self.assertNotEqual(ch2.channel_id, 1)
+
+ ch.close()
+ ch2.close()
+
+ def test_close(self):
+ """Make sure we've broken various references when closing
+ channels and connections, to help with GC."""
+ #
+ # Create a channel and make sure it's linked as we'd expect
+ #
+ ch = self.conn.channel()
+ self.assertEqual(1 in self.conn.channels, True)
+ self.assertEqual(ch.connection, self.conn)
+ self.assertEqual(ch.is_open, True)
+
+ #
+ # Close the channel and make sure the references are broken
+ # that we expect.
+ #
+ ch.close()
+ self.assertEqual(ch.connection, None)
+ self.assertEqual(1 in self.conn.channels, False)
+ self.assertEqual(ch.callbacks, {})
+ self.assertEqual(ch.is_open, False)
+
+ #
+ # Close the connection and make sure the references we expect
+ # are gone.
+ #
+ self.conn.close()
+ self.assertEqual(self.conn.connection, None)
+ self.assertEqual(self.conn.channels, None)
+
+ def test_gc_closed(self):
+ """Make sure we've broken various references when closing
+ channels and connections, to help with GC.
+
+ gc.garbage: http://docs.python.org/library/gc.html#gc.garbage
+ "A list of objects which the collector found to be
+ unreachable but could not be freed (uncollectable objects)."
+
+ """
+ unreachable_before = len(gc.garbage)
+ #
+ # Create a channel and make sure it's linked as we'd expect
+ #
+ self.conn.channel()
+ self.assertEqual(1 in self.conn.channels, True)
+
+ #
+ # Close the connection and make sure the references we expect
+ # are gone.
+ #
+ self.conn.close()
+
+ gc.collect()
+ gc.collect()
+ gc.collect()
+ self.assertEqual(unreachable_before, len(gc.garbage))
+
+ def test_gc_forget(self):
+ """Make sure the connection gets gc'ed when there is no more
+ references to it."""
+ unreachable_before = len(gc.garbage)
+
+ ch = self.conn.channel()
+ self.assertEqual(1 in self.conn.channels, True)
+
+ # remove all the references
+ self.conn = None
+ del(ch)
+
+ gc.collect()
+ gc.collect()
+ gc.collect()
+ self.assertEqual(unreachable_before, len(gc.garbage))
+
+
+def main():
+ suite = unittest.TestLoader().loadTestsFromTestCase(TestConnection)
+ unittest.TextTestRunner(**settings.test_args).run(suite)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/funtests/test_exceptions.py b/funtests/test_exceptions.py
new file mode 100755
index 0000000..f418135
--- /dev/null
+++ b/funtests/test_exceptions.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+"""
+Test amqp.exceptions module
+
+"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+
+import unittest
+
+import settings
+
+from amqp.exceptions import AMQPError
+
+
+class TestException(unittest.TestCase):
+ def test_exception(self):
+ exc = AMQPError('My Error', (10, 10), reply_code=7)
+ self.assertEqual(exc.reply_code, 7)
+ self.assertEqual(exc.reply_text, 'My Error')
+ self.assertEqual(exc.method_sig, (10, 10))
+ self.assertEqual(
+ exc.args,
+ (7, 'My Error', (10, 10), 'Connection.start'),
+ )
+
+
+def main():
+ suite = unittest.TestLoader().loadTestsFromTestCase(TestException)
+ unittest.TextTestRunner(**settings.test_args).run(suite)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/funtests/test_serialization.py b/funtests/test_serialization.py
new file mode 100755
index 0000000..ce70438
--- /dev/null
+++ b/funtests/test_serialization.py
@@ -0,0 +1,411 @@
+#!/usr/bin/env python
+"""
+Test amqp.serialization, checking conversions
+between byte streams and higher level objects.
+
+"""
+# Copyright (C) 2007-2008 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+
+from datetime import datetime
+from decimal import Decimal
+from random import randint
+import sys
+import unittest
+
+import settings
+
+from amqp.serialization import (
+ AMQPReader, AMQPWriter, GenericContent, FrameSyntaxError,
+)
+
+
+class TestSerialization(unittest.TestCase):
+
+ if sys.version_info[0] >= 3:
+
+ def assertEqualBinary(self, b, s):
+ """
+ Helper for Py3k Compatibility
+
+ """
+ self.assertEqual(b, s.encode('latin_1'))
+ else:
+ assertEqualBinary = unittest.TestCase.assertEqual
+
+ def test_empty_writer(self):
+ w = AMQPWriter()
+ self.assertEqual(w.getvalue(), bytes())
+
+ #
+ # Bits
+ #
+ def test_single_bit(self):
+ for val, check in [(True, '\x01'), (False, '\x00')]:
+ w = AMQPWriter()
+ w.write_bit(val)
+ s = w.getvalue()
+
+ self.assertEqualBinary(s, check)
+
+ r = AMQPReader(s)
+ self.assertEqual(r.read_bit(), val)
+
+ def test_multiple_bits(self):
+ w = AMQPWriter()
+ w.write_bit(True)
+ w.write_bit(True)
+ w.write_bit(False)
+ w.write_bit(True)
+ s = w.getvalue()
+
+ self.assertEqualBinary(s, '\x0b')
+
+ r = AMQPReader(s)
+ self.assertEqual(r.read_bit(), True)
+ self.assertEqual(r.read_bit(), True)
+ self.assertEqual(r.read_bit(), False)
+ self.assertEqual(r.read_bit(), True)
+
+ def test_multiple_bits2(self):
+ """
+ Check bits mixed with non-bits
+ """
+ w = AMQPWriter()
+ w.write_bit(True)
+ w.write_bit(True)
+ w.write_bit(False)
+ w.write_octet(10)
+ w.write_bit(True)
+ s = w.getvalue()
+
+ self.assertEqualBinary(s, '\x03\x0a\x01')
+
+ r = AMQPReader(s)
+ self.assertEqual(r.read_bit(), True)
+ self.assertEqual(r.read_bit(), True)
+ self.assertEqual(r.read_bit(), False)
+ self.assertEqual(r.read_octet(), 10)
+ self.assertEqual(r.read_bit(), True)
+
+ def test_multiple_bits3(self):
+ """
+ Check bit groups that span multiple bytes
+ """
+ w = AMQPWriter()
+
+ # Spit out 20 bits
+ for i in range(10):
+ w.write_bit(True)
+ w.write_bit(False)
+
+ s = w.getvalue()
+
+ self.assertEqualBinary(s, '\x55\x55\x05')
+
+ r = AMQPReader(s)
+ for i in range(10):
+ self.assertEqual(r.read_bit(), True)
+ self.assertEqual(r.read_bit(), False)
+
+ #
+ # Octets
+ #
+ def test_octet(self):
+ for val in range(256):
+ w = AMQPWriter()
+ w.write_octet(val)
+ s = w.getvalue()
+ self.assertEqualBinary(s, chr(val))
+
+ r = AMQPReader(s)
+ self.assertEqual(r.read_octet(), val)
+
+ def test_octet_invalid(self):
+ w = AMQPWriter()
+ self.assertRaises(FrameSyntaxError, w.write_octet, -1)
+
+ def test_octet_invalid2(self):
+ w = AMQPWriter()
+ self.assertRaises(FrameSyntaxError, w.write_octet, 256)
+
+ #
+ # Shorts
+ #
+ def test_short(self):
+ for i in range(256):
+ val = randint(0, 65535)
+ w = AMQPWriter()
+ w.write_short(val)
+ s = w.getvalue()
+
+ r = AMQPReader(s)
+ self.assertEqual(r.read_short(), val)
+
+ def test_short_invalid(self):
+ w = AMQPWriter()
+ self.assertRaises(FrameSyntaxError, w.write_short, -1)
+
+ def test_short_invalid2(self):
+ w = AMQPWriter()
+ self.assertRaises(FrameSyntaxError, w.write_short, 65536)
+
+ #
+ # Longs
+ #
+ def test_long(self):
+ for i in range(256):
+ val = randint(0, 4294967295)
+ w = AMQPWriter()
+ w.write_long(val)
+ s = w.getvalue()
+
+ r = AMQPReader(s)
+ self.assertEqual(r.read_long(), val)
+
+ def test_long_invalid(self):
+ w = AMQPWriter()
+ self.assertRaises(FrameSyntaxError, w.write_long, -1)
+
+ def test_long_invalid2(self):
+ w = AMQPWriter()
+ self.assertRaises(FrameSyntaxError, w.write_long, 4294967296)
+
+ #
+ # LongLongs
+ #
+ def test_longlong(self):
+ for i in range(256):
+ val = randint(0, (2 ** 64) - 1)
+ w = AMQPWriter()
+ w.write_longlong(val)
+ s = w.getvalue()
+
+ r = AMQPReader(s)
+ self.assertEqual(r.read_longlong(), val)
+
+ def test_longlong_invalid(self):
+ w = AMQPWriter()
+ self.assertRaises(FrameSyntaxError, w.write_longlong, -1)
+
+ def test_longlong_invalid2(self):
+ w = AMQPWriter()
+ self.assertRaises(FrameSyntaxError, w.write_longlong, 2 ** 64)
+
+ #
+ # Shortstr
+ #
+ def test_empty_shortstr(self):
+ w = AMQPWriter()
+ w.write_shortstr('')
+ s = w.getvalue()
+
+ self.assertEqualBinary(s, '\x00')
+
+ r = AMQPReader(s)
+ self.assertEqual(r.read_shortstr(), '')
+
+ def test_shortstr(self):
+ w = AMQPWriter()
+ w.write_shortstr('hello')
+ s = w.getvalue()
+ self.assertEqualBinary(s, '\x05hello')
+
+ r = AMQPReader(s)
+ self.assertEqual(r.read_shortstr(), 'hello')
+
+ def test_shortstr_unicode(self):
+ w = AMQPWriter()
+ w.write_shortstr(u'hello')
+ s = w.getvalue()
+ self.assertEqualBinary(s, '\x05hello')
+
+ r = AMQPReader(s)
+ self.assertEqual(r.read_shortstr(), u'hello')
+
+ def test_long_shortstr(self):
+ w = AMQPWriter()
+ self.assertRaises(FrameSyntaxError, w.write_shortstr, 'x' * 256)
+
+ def test_long_shortstr_unicode(self):
+ w = AMQPWriter()
+ self.assertRaises(FrameSyntaxError, w.write_shortstr, u'\u0100' * 128)
+
+ #
+ # Longstr
+ #
+
+ def test_empty_longstr(self):
+ w = AMQPWriter()
+ w.write_longstr('')
+ s = w.getvalue()
+
+ self.assertEqualBinary(s, '\x00\x00\x00\x00')
+
+ r = AMQPReader(s)
+ self.assertEqual(r.read_longstr(), '')
+
+ def test_longstr(self):
+ val = 'a' * 512
+ w = AMQPWriter()
+ w.write_longstr(val)
+ s = w.getvalue()
+
+ self.assertEqualBinary(s, '\x00\x00\x02\x00' + ('a' * 512))
+
+ r = AMQPReader(s)
+ self.assertEqual(r.read_longstr(), str(val))
+
+ def test_longstr_unicode(self):
+ val = u'a' * 512
+ w = AMQPWriter()
+ w.write_longstr(val)
+ s = w.getvalue()
+
+ self.assertEqualBinary(s, '\x00\x00\x02\x00' + ('a' * 512))
+
+ r = AMQPReader(s)
+ self.assertEqual(r.read_longstr(), val)
+
+ #
+ # Table
+ #
+ def test_table_empty(self):
+ val = {}
+ w = AMQPWriter()
+ w.write_table(val)
+ s = w.getvalue()
+
+ self.assertEqualBinary(s, '\x00\x00\x00\x00')
+
+ r = AMQPReader(s)
+ self.assertEqual(r.read_table(), val)
+
+ def test_table(self):
+ val = {'foo': 7}
+ w = AMQPWriter()
+ w.write_table(val)
+ s = w.getvalue()
+
+ self.assertEqualBinary(s, '\x00\x00\x00\x09\x03fooI\x00\x00\x00\x07')
+
+ r = AMQPReader(s)
+ self.assertEqual(r.read_table(), val)
+
+ def test_table_invalid(self):
+ """
+ Check that an un-serializable table entry raises a ValueError
+
+ """
+ val = {'test': object()}
+ w = AMQPWriter()
+ self.assertRaises(FrameSyntaxError, w.write_table, val)
+
+ def test_table_multi(self):
+ val = {
+ 'foo': 7,
+ 'bar': Decimal('123345.1234'),
+ 'baz': 'this is some random string I typed',
+ 'ubaz': u'And something in unicode',
+ 'dday_aniv': datetime(1994, 6, 6),
+ 'nothing': None,
+ 'more': {
+ 'abc': -123,
+ 'def': 'hello world',
+ 'now': datetime(2007, 11, 11, 21, 14, 31),
+ 'qty': Decimal('-123.45'),
+ 'blank': {},
+ 'extra': {
+ 'deeper': 'more strings',
+ 'nums': -12345678,
+ },
+ }
+ }
+
+ w = AMQPWriter()
+ w.write_table(val)
+ s = w.getvalue()
+
+ r = AMQPReader(s)
+ self.assertEqual(r.read_table(), val)
+
+ #
+ # Array
+ #
+ def test_array_from_list(self):
+ val = [1, 'foo', None]
+ w = AMQPWriter()
+ w.write_array(val)
+ s = w.getvalue()
+
+ self.assertEqualBinary(
+ s, '\x00\x00\x00\x0EI\x00\x00\x00\x01S\x00\x00\x00\x03fooV',
+ )
+
+ r = AMQPReader(s)
+ self.assertEqual(r.read_array(), val)
+
+ def test_array_from_tuple(self):
+ val = (1, 'foo', None)
+ w = AMQPWriter()
+ w.write_array(val)
+ s = w.getvalue()
+
+ self.assertEqualBinary(
+ s, '\x00\x00\x00\x0EI\x00\x00\x00\x01S\x00\x00\x00\x03fooV',
+ )
+
+ r = AMQPReader(s)
+ self.assertEqual(r.read_array(), list(val))
+
+ def test_table_with_array(self):
+ val = {
+ 'foo': 7,
+ 'bar': Decimal('123345.1234'),
+ 'baz': 'this is some random string I typed',
+ 'blist': [1, 2, 3],
+ 'nlist': [1, [2, 3, 4]],
+ 'ndictl': {'nfoo': 8, 'nblist': [5, 6, 7]}
+ }
+
+ w = AMQPWriter()
+ w.write_table(val)
+ s = w.getvalue()
+
+ r = AMQPReader(s)
+ self.assertEqual(r.read_table(), val)
+
+ #
+ # GenericContent
+ #
+ def test_generic_content_eq(self):
+ msg_1 = GenericContent(dummy='foo')
+ msg_2 = GenericContent(dummy='foo')
+ msg_3 = GenericContent(dummy='bar')
+
+ self.assertEqual(msg_1, msg_1)
+ self.assertEqual(msg_1, msg_2)
+ self.assertNotEqual(msg_1, msg_3)
+ self.assertNotEqual(msg_1, None)
+
+
+def main():
+ suite = unittest.TestLoader().loadTestsFromTestCase(TestSerialization)
+ unittest.TextTestRunner(**settings.test_args).run(suite)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/funtests/test_with.py b/funtests/test_with.py
new file mode 100644
index 0000000..b479794
--- /dev/null
+++ b/funtests/test_with.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+"""
+Test support for 'with' statements
+
+"""
+# Copyright (C) 2009 Barry Pederson <bp@barryp.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+
+import unittest
+
+import settings
+
+from amqp import Connection, Message
+
+
+class TestChannel(unittest.TestCase):
+
+ def test_with(self):
+ with Connection(**settings.connect_args) as conn:
+ self.assertEqual(conn.transport is None, False)
+
+ with conn.channel(1) as ch:
+ self.assertEqual(1 in conn.channels, True)
+
+ #
+ # Do something with the channel
+ #
+ ch.exchange_declare('unittest.fanout', 'fanout',
+ auto_delete=True)
+
+ msg = Message(
+ 'unittest message',
+ content_type='text/plain',
+ application_headers={'foo': 7, 'bar': 'baz'},
+ )
+
+ ch.basic_publish(msg, 'unittest.fanout')
+
+ #
+ # check that the channel was closed
+ #
+ self.assertEqual(1 in conn.channels, False)
+ self.assertEqual(ch.is_open, False)
+
+ #
+ # Check that the connection was closed
+ #
+ self.assertEqual(conn.transport, None)
+
+
+def main():
+ suite = unittest.TestLoader().loadTestsFromTestCase(TestChannel)
+ unittest.TextTestRunner(**settings.test_args).run(suite)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/requirements/docs.txt b/requirements/docs.txt
new file mode 100644
index 0000000..0f57c43
--- /dev/null
+++ b/requirements/docs.txt
@@ -0,0 +1,2 @@
+Sphinx
+sphinxcontrib-issuetracker>=0.9
diff --git a/requirements/pkgutils.txt b/requirements/pkgutils.txt
new file mode 100644
index 0000000..2e522fc
--- /dev/null
+++ b/requirements/pkgutils.txt
@@ -0,0 +1,5 @@
+paver
+flake8
+flakeplus
+tox
+Sphinx-PyPI-upload
diff --git a/requirements/test.txt b/requirements/test.txt
new file mode 100644
index 0000000..7554730
--- /dev/null
+++ b/requirements/test.txt
@@ -0,0 +1,5 @@
+unittest2>=0.4.0
+nose
+nose-cover3
+coverage>=3.0
+mock
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..861a9f5
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build =
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..a1cfed5
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+try:
+ from setuptools import setup, find_packages
+ from setuptools.command.test import test
+except ImportError:
+ raise
+ from ez_setup import use_setuptools
+ use_setuptools()
+ from setuptools import setup, find_packages # noqa
+ from setuptools.command.test import test # noqa
+
+import os
+import sys
+import codecs
+
+if sys.version_info < (2, 6):
+ raise Exception('amqp requires Python 2.6 or higher.')
+
+NAME = 'amqp'
+entrypoints = {}
+extra = {}
+
+# -*- Classifiers -*-
+
+classes = """
+ Development Status :: 5 - Production/Stable
+ Programming Language :: Python
+ Programming Language :: Python :: 2
+ Programming Language :: Python :: 2.6
+ Programming Language :: Python :: 2.7
+ Programming Language :: Python :: 3
+ Programming Language :: Python :: 3.0
+ Programming Language :: Python :: 3.1
+ Programming Language :: Python :: 3.2
+ Programming Language :: Python :: 3.3
+ License :: OSI Approved :: GNU Library or \
+Lesser General Public License (LGPL)
+ Intended Audience :: Developers
+ License :: OSI Approved :: BSD License
+ Operating System :: OS Independent
+"""
+classifiers = [s.strip() for s in classes.split('\n') if s]
+
+# -*- Distribution Meta -*-
+
+import re
+re_meta = re.compile(r'__(\w+?)__\s*=\s*(.*)')
+re_vers = re.compile(r'VERSION\s*=\s*\((.*?)\)')
+re_doc = re.compile(r'^"""(.+?)"""')
+rq = lambda s: s.strip("\"'")
+
+
+def add_default(m):
+ attr_name, attr_value = m.groups()
+ return ((attr_name, rq(attr_value)), )
+
+
+def add_version(m):
+ v = list(map(rq, m.groups()[0].split(', ')))
+ return (('VERSION', '.'.join(v[0:3]) + ''.join(v[3:])), )
+
+
+def add_doc(m):
+ return (('doc', m.groups()[0]), )
+
+pats = {re_meta: add_default,
+ re_vers: add_version,
+ re_doc: add_doc}
+here = os.path.abspath(os.path.dirname(__file__))
+with open(os.path.join(here, 'amqp/__init__.py')) as meta_fh:
+ meta = {}
+ for line in meta_fh:
+ if line.strip() == '# -eof meta-':
+ break
+ for pattern, handler in pats.items():
+ m = pattern.match(line.strip())
+ if m:
+ meta.update(handler(m))
+
+# -*- Installation Requires -*-
+
+py_version = sys.version_info
+is_jython = sys.platform.startswith('java')
+is_pypy = hasattr(sys, 'pypy_version_info')
+
+
+def strip_comments(l):
+ return l.split('#', 1)[0].strip()
+
+
+def reqs(f):
+ return filter(None, [strip_comments(l) for l in open(
+ os.path.join(os.getcwd(), 'requirements', f)).readlines()])
+
+install_requires = []
+
+# -*- Tests Requires -*-
+
+tests_require = reqs('test.txt')
+
+# -*- Long Description -*-
+
+if os.path.exists('README.rst'):
+ long_description = codecs.open('README.rst', 'r', 'utf-8').read()
+else:
+ long_description = 'See http://pypi.python.org/pypi/amqp'
+
+# -*- Entry Points -*- #
+
+# -*- %%% -*-
+
+setup(
+ name=NAME,
+ version=meta['VERSION'],
+ description=meta['doc'],
+ author=meta['author'],
+ author_email=meta['contact'],
+ maintainer=meta['maintainer'],
+ url=meta['homepage'],
+ platforms=['any'],
+ license='LGPL',
+ packages=find_packages(exclude=['ez_setup', 'tests', 'tests.*']),
+ zip_safe=False,
+ install_requires=install_requires,
+ tests_require=tests_require,
+ test_suite='nose.collector',
+ classifiers=classifiers,
+ entry_points=entrypoints,
+ long_description=long_description,
+ **extra)