Initial import from tarball

This commit is contained in:
Jérôme Schneider 2014-11-01 18:56:02 +01:00
commit 92d64728d8
69 changed files with 10771 additions and 0 deletions

484
Changelog Normal file
View File

@ -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

458
LICENSE Normal file
View File

@ -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

6
MANIFEST.in Normal file
View File

@ -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

128
PKG-INFO Normal file
View File

@ -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

104
README.rst Normal file
View File

@ -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

128
amqp.egg-info/PKG-INFO Normal file
View File

@ -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

67
amqp.egg-info/SOURCES.txt Normal file
View File

@ -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

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@
amqp

70
amqp/__init__.py Normal file
View File

@ -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

93
amqp/abstract_channel.py Normal file
View File

@ -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 = {}

124
amqp/basic_message.py Normal file
View File

@ -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

2537
amqp/channel.py Normal file

File diff suppressed because it is too large Load Diff

1004
amqp/connection.py Normal file

File diff suppressed because it is too large Load Diff

258
amqp/exceptions.py Normal file
View File

@ -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

188
amqp/five.py Normal file
View File

@ -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

231
amqp/method_framing.py Normal file
View File

@ -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

13
amqp/protocol.py Normal file
View File

@ -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'),
)

510
amqp/serialization.py Normal file
View File

@ -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()

294
amqp/transport.py Normal file
View File

@ -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)

102
amqp/utils.py Normal file
View File

@ -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

78
demo/amqp_clock.py Executable file
View File

@ -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()

83
demo/demo_receive.py Executable file
View File

@ -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()

66
demo/demo_send.py Executable file
View File

@ -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
docs/.static/.keep Normal file
View File

View File

@ -0,0 +1,4 @@
{% extends "layout.html" %}
{% block body %}
{{ body }}
{% endblock %}

View File

@ -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>

View File

@ -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>

81
docs/Makefile Normal file
View File

@ -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."

92
docs/_ext/applyxrefs.py Normal file
View File

@ -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())

View File

@ -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

401
docs/_theme/celery/static/celery.css_t vendored Normal file
View File

@ -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;
}

5
docs/_theme/celery/theme.conf vendored Normal file
View File

@ -0,0 +1,5 @@
[theme]
inherit = basic
stylesheet = celery.css
[options]

484
docs/changelog.rst Normal file
View File

@ -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

127
docs/conf.py Normal file
View File

@ -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

96
docs/includes/intro.txt Normal file
View File

@ -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

22
docs/index.rst Normal file
View File

@ -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`

View File

@ -0,0 +1,11 @@
=====================================================
amqp.abstract_channel
=====================================================
.. contents::
:local:
.. currentmodule:: amqp.abstract_channel
.. automodule:: amqp.abstract_channel
:members:
:undoc-members:

View File

@ -0,0 +1,11 @@
=====================================================
amqp.basic_message
=====================================================
.. contents::
:local:
.. currentmodule:: amqp.basic_message
.. automodule:: amqp.basic_message
:members:
:undoc-members:

View File

@ -0,0 +1,11 @@
=====================================================
amqp.channel
=====================================================
.. contents::
:local:
.. currentmodule:: amqp.channel
.. automodule:: amqp.channel
:members:
:undoc-members:

View File

@ -0,0 +1,11 @@
=====================================================
amqp.connection
=====================================================
.. contents::
:local:
.. currentmodule:: amqp.connection
.. automodule:: amqp.connection
:members:
:undoc-members:

View File

@ -0,0 +1,11 @@
=====================================================
amqp.exceptions
=====================================================
.. contents::
:local:
.. currentmodule:: amqp.exceptions
.. automodule:: amqp.exceptions
:members:
:undoc-members:

View File

@ -0,0 +1,11 @@
=====================================================
amqp.five
=====================================================
.. contents::
:local:
.. currentmodule:: amqp.five
.. automodule:: amqp.five
:members:
:undoc-members:

View File

@ -0,0 +1,11 @@
=====================================================
amqp.method_framing
=====================================================
.. contents::
:local:
.. currentmodule:: amqp.method_framing
.. automodule:: amqp.method_framing
:members:
:undoc-members:

View File

@ -0,0 +1,11 @@
=====================================================
amqp.protocol
=====================================================
.. contents::
:local:
.. currentmodule:: amqp.protocol
.. automodule:: amqp.protocol
:members:
:undoc-members:

View File

@ -0,0 +1,11 @@
=====================================================
amqp.serialization
=====================================================
.. contents::
:local:
.. currentmodule:: amqp.serialization
.. automodule:: amqp.serialization
:members:
:undoc-members:

View File

@ -0,0 +1,11 @@
=====================================================
amqp.transport
=====================================================
.. contents::
:local:
.. currentmodule:: amqp.transport
.. automodule:: amqp.transport
:members:
:undoc-members:

View File

@ -0,0 +1,11 @@
=====================================================
amqp.utils
=====================================================
.. contents::
:local:
.. currentmodule:: amqp.utils
.. automodule:: amqp.utils
:members:
:undoc-members:

23
docs/reference/index.rst Normal file
View File

@ -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

5
docs/templates/readme.txt vendored Normal file
View File

@ -0,0 +1,5 @@
=====================================================================
Python AMQP 0.9.1 client library
=====================================================================
.. include:: ../includes/intro.txt

10
extra/README Normal file
View File

@ -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

377
extra/generate_skeleton_0_8.py Executable file
View File

@ -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())

181
extra/release/bump_version.py Executable file
View File

@ -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()

75
extra/release/sphinx-to-rst.py Executable file
View File

@ -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()

View File

@ -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())

38
funtests/run_all.py Executable file
View File

@ -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()

91
funtests/settings.py Normal file
View File

@ -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()

132
funtests/test_basic_message.py Executable file
View File

@ -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()

317
funtests/test_channel.py Executable file
View File

@ -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()

127
funtests/test_connection.py Executable file
View File

@ -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()

47
funtests/test_exceptions.py Executable file
View File

@ -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()

411
funtests/test_serialization.py Executable file
View File

@ -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()

70
funtests/test_with.py Normal file
View File

@ -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()

2
requirements/docs.txt Normal file
View File

@ -0,0 +1,2 @@
Sphinx
sphinxcontrib-issuetracker>=0.9

View File

@ -0,0 +1,5 @@
paver
flake8
flakeplus
tox
Sphinx-PyPI-upload

5
requirements/test.txt Normal file
View File

@ -0,0 +1,5 @@
unittest2>=0.4.0
nose
nose-cover3
coverage>=3.0
mock

5
setup.cfg Normal file
View File

@ -0,0 +1,5 @@
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0

132
setup.py Normal file
View File

@ -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)