Initial import from tarball
This commit is contained in:
commit
92d64728d8
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
amqp
|
|
@ -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
|
|
@ -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 = {}
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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'),
|
||||
)
|
|
@ -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()
|
|
@ -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)
|
|
@ -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
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -0,0 +1,4 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
{{ body }}
|
||||
{% endblock %}
|
|
@ -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>
|
||||
|
|
@ -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>
|
|
@ -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."
|
|
@ -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())
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = celery.css
|
||||
|
||||
[options]
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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`
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
=====================================================
|
||||
amqp.abstract_channel
|
||||
=====================================================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
.. currentmodule:: amqp.abstract_channel
|
||||
|
||||
.. automodule:: amqp.abstract_channel
|
||||
:members:
|
||||
:undoc-members:
|
|
@ -0,0 +1,11 @@
|
|||
=====================================================
|
||||
amqp.basic_message
|
||||
=====================================================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
.. currentmodule:: amqp.basic_message
|
||||
|
||||
.. automodule:: amqp.basic_message
|
||||
:members:
|
||||
:undoc-members:
|
|
@ -0,0 +1,11 @@
|
|||
=====================================================
|
||||
amqp.channel
|
||||
=====================================================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
.. currentmodule:: amqp.channel
|
||||
|
||||
.. automodule:: amqp.channel
|
||||
:members:
|
||||
:undoc-members:
|
|
@ -0,0 +1,11 @@
|
|||
=====================================================
|
||||
amqp.connection
|
||||
=====================================================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
.. currentmodule:: amqp.connection
|
||||
|
||||
.. automodule:: amqp.connection
|
||||
:members:
|
||||
:undoc-members:
|
|
@ -0,0 +1,11 @@
|
|||
=====================================================
|
||||
amqp.exceptions
|
||||
=====================================================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
.. currentmodule:: amqp.exceptions
|
||||
|
||||
.. automodule:: amqp.exceptions
|
||||
:members:
|
||||
:undoc-members:
|
|
@ -0,0 +1,11 @@
|
|||
=====================================================
|
||||
amqp.five
|
||||
=====================================================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
.. currentmodule:: amqp.five
|
||||
|
||||
.. automodule:: amqp.five
|
||||
:members:
|
||||
:undoc-members:
|
|
@ -0,0 +1,11 @@
|
|||
=====================================================
|
||||
amqp.method_framing
|
||||
=====================================================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
.. currentmodule:: amqp.method_framing
|
||||
|
||||
.. automodule:: amqp.method_framing
|
||||
:members:
|
||||
:undoc-members:
|
|
@ -0,0 +1,11 @@
|
|||
=====================================================
|
||||
amqp.protocol
|
||||
=====================================================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
.. currentmodule:: amqp.protocol
|
||||
|
||||
.. automodule:: amqp.protocol
|
||||
:members:
|
||||
:undoc-members:
|
|
@ -0,0 +1,11 @@
|
|||
=====================================================
|
||||
amqp.serialization
|
||||
=====================================================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
.. currentmodule:: amqp.serialization
|
||||
|
||||
.. automodule:: amqp.serialization
|
||||
:members:
|
||||
:undoc-members:
|
|
@ -0,0 +1,11 @@
|
|||
=====================================================
|
||||
amqp.transport
|
||||
=====================================================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
.. currentmodule:: amqp.transport
|
||||
|
||||
.. automodule:: amqp.transport
|
||||
:members:
|
||||
:undoc-members:
|
|
@ -0,0 +1,11 @@
|
|||
=====================================================
|
||||
amqp.utils
|
||||
=====================================================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
.. currentmodule:: amqp.utils
|
||||
|
||||
.. automodule:: amqp.utils
|
||||
:members:
|
||||
:undoc-members:
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
=====================================================================
|
||||
Python AMQP 0.9.1 client library
|
||||
=====================================================================
|
||||
|
||||
.. include:: ../includes/intro.txt
|
|
@ -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
|
|
@ -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())
|
|
@ -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()
|
|
@ -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()
|
|
@ -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())
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -0,0 +1,2 @@
|
|||
Sphinx
|
||||
sphinxcontrib-issuetracker>=0.9
|
|
@ -0,0 +1,5 @@
|
|||
paver
|
||||
flake8
|
||||
flakeplus
|
||||
tox
|
||||
Sphinx-PyPI-upload
|
|
@ -0,0 +1,5 @@
|
|||
unittest2>=0.4.0
|
||||
nose
|
||||
nose-cover3
|
||||
coverage>=3.0
|
||||
mock
|
|
@ -0,0 +1,5 @@
|
|||
[egg_info]
|
||||
tag_build =
|
||||
tag_date = 0
|
||||
tag_svn_revision = 0
|
||||
|
|
@ -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)
|
Loading…
Reference in New Issue