amqplib-1.0.2/0000755000175100017510000000000011641133256013763 5ustar barrypbarryp00000000000000amqplib-1.0.2/LICENSE0000644000175100017510000005750610735225412015004 0ustar barrypbarryp00000000000000 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 amqplib-1.0.2/TODO0000644000175100017510000000071110735225412014451 0ustar barrypbarryp00000000000000Check how Decimals should be encoded in tables. The docs are a bit unclear, and looking at RabbitMQ and py-qpid didn't help much. At least it should work when the sender and receiver are both using this version of this library. Error handling, a bit rough in this first version, receiving unexpected frames may do strange things. Nothing is done at all with heartbeat, oob-method, oob-header oob-body, and trace frames. Unittests - always can use more amqplib-1.0.2/demo/0000755000175100017510000000000011641133256014707 5ustar barrypbarryp00000000000000amqplib-1.0.2/demo/demo_receive.py0000755000175100017510000000375211544265731017727 0ustar barrypbarryp00000000000000#!/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 """ from optparse import OptionParser import amqplib.client_0_8 as amqp def callback(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 ('-------') msg.channel.basic_ack(msg.delivery_tag) # # Cancel this callback # if msg.body == 'quit': msg.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.access_request('/data', active=True, read=True) ch.exchange_declare('myfan', 'fanout', auto_delete=True) qname, _, _ = ch.queue_declare() ch.queue_bind(qname, 'myfan') ch.basic_consume(qname, callback=callback) # # Loop as long as the channel has callbacks registered # while ch.callbacks: ch.wait() ch.close() conn.close() if __name__ == '__main__': main() amqplib-1.0.2/demo/demo_send.py0000755000175100017510000000330110735225412017215 0ustar barrypbarryp00000000000000#!/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 """ import sys import time from optparse import OptionParser import amqplib.client_0_8 as 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.access_request('/data', active=True, write=True) ch.exchange_declare('myfan', 'fanout', auto_delete=True) 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() amqplib-1.0.2/demo/amqp_clock.py0000755000175100017510000000445011105620756017401 0ustar barrypbarryp00000000000000#!/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 """ from datetime import datetime from optparse import OptionParser from time import sleep import amqplib.client_0_8 as 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.access_request('/data', write=True, active=True) 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() amqplib-1.0.2/amqplib/0000755000175100017510000000000011641133256015410 5ustar barrypbarryp00000000000000amqplib-1.0.2/amqplib/client_0_8/0000755000175100017510000000000011641133256017334 5ustar barrypbarryp00000000000000amqplib-1.0.2/amqplib/client_0_8/exceptions.py0000644000175100017510000000663311135126760022077 0ustar barrypbarryp00000000000000""" Exceptions used by amqplib.client_0_8 """ # Copyright (C) 2007-2008 Barry Pederson # # 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 __all__ = [ 'AMQPException', 'AMQPConnectionException', 'AMQPChannelException', ] class AMQPException(Exception): def __init__(self, reply_code, reply_text, method_sig): Exception.__init__(self) self.amqp_reply_code = reply_code self.amqp_reply_text = reply_text self.amqp_method_sig = method_sig self.args = ( reply_code, reply_text, method_sig, METHOD_NAME_MAP.get(method_sig, '') ) class AMQPConnectionException(AMQPException): pass class AMQPChannelException(AMQPException): pass 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.redirect', (10, 60): 'Connection.close', (10, 61): 'Connection.close_ok', (20, 10): 'Channel.open', (20, 11): 'Channel.open_ok', (20, 20): 'Channel.flow', (20, 21): 'Channel.flow_ok', (20, 30): 'Channel.alert', (20, 40): 'Channel.close', (20, 41): 'Channel.close_ok', (30, 10): 'Channel.access_request', (30, 11): 'Channel.access_request_ok', (40, 10): 'Channel.exchange_declare', (40, 11): 'Channel.exchange_declare_ok', (40, 20): 'Channel.exchange_delete', (40, 21): 'Channel.exchange_delete_ok', (50, 10): 'Channel.queue_declare', (50, 11): 'Channel.queue_declare_ok', (50, 20): 'Channel.queue_bind', (50, 21): 'Channel.queue_bind_ok', (50, 30): 'Channel.queue_purge', (50, 31): 'Channel.queue_purge_ok', (50, 40): 'Channel.queue_delete', (50, 41): 'Channel.queue_delete_ok', (60, 10): 'Channel.basic_qos', (60, 11): 'Channel.basic_qos_ok', (60, 20): 'Channel.basic_consume', (60, 21): 'Channel.basic_consume_ok', (60, 30): 'Channel.basic_cancel', (60, 31): 'Channel.basic_cancel_ok', (60, 40): 'Channel.basic_publish', (60, 50): 'Channel.basic_return', (60, 60): 'Channel.basic_deliver', (60, 70): 'Channel.basic_get', (60, 71): 'Channel.basic_get_ok', (60, 72): 'Channel.basic_get_empty', (60, 80): 'Channel.basic_ack', (60, 90): 'Channel.basic_reject', (60, 100): 'Channel.basic_recover', (90, 10): 'Channel.tx_select', (90, 11): 'Channel.tx_select_ok', (90, 20): 'Channel.tx_commit', (90, 21): 'Channel.tx_commit_ok', (90, 30): 'Channel.tx_rollback', (90, 31): 'Channel.tx_rollback_ok', } amqplib-1.0.2/amqplib/client_0_8/basic_message.py0000644000175100017510000000744411610732570022504 0ustar barrypbarryp00000000000000""" Messages for AMQP """ # Copyright (C) 2007-2008 Barry Pederson # # 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 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, **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 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. """ return super(Message, self).__eq__(other) \ and hasattr(other, 'body') and (self.body == other.body) amqplib-1.0.2/amqplib/client_0_8/channel.py0000644000175100017510000025161311600523106021316 0ustar barrypbarryp00000000000000""" AMQP 0-8 Channels """ # Copyright (C) 2007-2008 Barry Pederson # # 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 Queue import Queue from abstract_channel import AbstractChannel from exceptions import * from serialization import AMQPWriter __all__ = [ 'Channel', # here mainly so it shows in in pydoc ] AMQP_LOGGER = logging.getLogger('amqplib') class Channel(AbstractChannel): """ work with channels The channel class provides methods for a client to establish a virtual connection - a channel - to a server and for both peers to operate the virtual connection thereafter. GRAMMAR: channel = open-channel *use-channel close-channel open-channel = C:OPEN S:OPEN-OK use-channel = C:FLOW S:FLOW-OK / S:FLOW C:FLOW-OK / S:ALERT / functional-class close-channel = C:CLOSE S:CLOSE-OK / S:CLOSE C:CLOSE-OK """ def __init__(self, connection, channel_id=None, auto_decode=True): """ Create a channel bound to a connection and using the specified numeric channel_id, and open on the server. The 'auto_decode' parameter (defaults to True), indicates whether the library should attempt to decode the body of Messages to a Unicode string if there's a 'content_encoding' property for the message. If there's no 'content_encoding' property, or the decode raises an Exception, the message body is left as plain bytes. """ if channel_id is None: channel_id = connection._get_free_channel_id() AMQP_LOGGER.debug('using channel_id: %d' % channel_id) super(Channel, self).__init__(connection, channel_id) self.default_ticket = 0 self.is_open = False self.active = True # Flow control self.alerts = Queue() self.returned_messages = Queue() self.callbacks = {} self.auto_decode = auto_decode self._x_open() def _do_close(self): """ Tear down this object, after we've agreed to close with the server. """ AMQP_LOGGER.debug('Closed channel #%d' % self.channel_id) self.is_open = False del self.connection.channels[self.channel_id] self.channel_id = self.connection = None self.callbacks = {} ################# def _alert(self, args): """ This method allows the server to send a non-fatal warning to the client. This is used for methods that are normally asynchronous and thus do not have confirmations, and for which the server may detect errors that need to be reported. Fatal errors are handled as channel or connection exceptions; non- fatal errors are sent through this method. PARAMETERS: reply_code: short The reply code. The AMQ reply codes are defined in AMQ RFC 011. reply_text: shortstr The localised reply text. This text can be logged as an aid to resolving issues. details: table detailed information for warning A set of fields that provide more information about the problem. The meaning of these fields are defined on a per-reply-code basis (TO BE DEFINED). """ reply_code = args.read_short() reply_text = args.read_shortstr() details = args.read_table() self.alerts.put((reply_code, reply_text, details)) def close(self, reply_code=0, reply_text='', method_sig=(0, 0)): """ request a channel close This method indicates that the sender wants to close the channel. This may be due to internal conditions (e.g. a forced shut-down) or due to an error handling a specific method, i.e. an exception. When a close is due to an exception, the sender provides the class and method id of the method which caused the exception. RULE: After sending this method any received method except Channel.Close-OK MUST be discarded. RULE: The peer sending this method MAY use a counter or timeout to detect failure of the other peer to respond correctly with Channel.Close-OK.. PARAMETERS: reply_code: short The reply code. The AMQ reply codes are defined in AMQ RFC 011. reply_text: shortstr The localised reply text. This text can be logged as an aid to resolving issues. class_id: short failing method class When the close is provoked by a method exception, this is the class of the method. method_id: short failing method ID When the close is provoked by a method exception, this is the ID of the method. """ if not self.is_open: # already closed return args = AMQPWriter() args.write_short(reply_code) args.write_shortstr(reply_text) args.write_short(method_sig[0]) # class_id args.write_short(method_sig[1]) # method_id self._send_method((20, 40), args) return self.wait(allowed_methods=[ (20, 41), # Channel.close_ok ]) def _close(self, args): """ request a channel close This method indicates that the sender wants to close the channel. This may be due to internal conditions (e.g. a forced shut-down) or due to an error handling a specific method, i.e. an exception. When a close is due to an exception, the sender provides the class and method id of the method which caused the exception. RULE: After sending this method any received method except Channel.Close-OK MUST be discarded. RULE: The peer sending this method MAY use a counter or timeout to detect failure of the other peer to respond correctly with Channel.Close-OK.. PARAMETERS: reply_code: short The reply code. The AMQ reply codes are defined in AMQ RFC 011. reply_text: shortstr The localised reply text. This text can be logged as an aid to resolving issues. class_id: short failing method class When the close is provoked by a method exception, this is the class of the method. method_id: short failing method ID When the close is provoked by a method exception, this is the ID of the method. """ reply_code = args.read_short() reply_text = args.read_shortstr() class_id = args.read_short() method_id = args.read_short() # self.close_ok() # def close_ok(self): # """ # confirm a channel close # # This method confirms a Channel.Close method and tells the # recipient that it is safe to release resources for the channel # and close the socket. # # RULE: # # A peer that detects a socket closure without having # received a Channel.Close-Ok handshake method SHOULD log # the error. # # """ self._send_method((20, 41)) self._do_close() raise AMQPChannelException(reply_code, reply_text, (class_id, method_id)) def _close_ok(self, args): """ confirm a channel close This method confirms a Channel.Close method and tells the recipient that it is safe to release resources for the channel and close the socket. RULE: A peer that detects a socket closure without having received a Channel.Close-Ok handshake method SHOULD log the error. """ self._do_close() def flow(self, active): """ enable/disable flow from peer This method asks the peer to pause or restart the flow of content data. This is a simple flow-control mechanism that a peer can use to avoid oveflowing its queues or otherwise finding itself receiving more messages than it can process. Note that this method is not intended for window control. The peer that receives a request to stop sending content should finish sending the current content, if any, and then wait until it receives a Flow restart method. RULE: When a new channel is opened, it is active. Some applications assume that channels are inactive until started. To emulate this behaviour a client MAY open the channel, then pause it. RULE: When sending content data in multiple frames, a peer SHOULD monitor the channel for incoming methods and respond to a Channel.Flow as rapidly as possible. RULE: A peer MAY use the Channel.Flow method to throttle incoming content data for internal reasons, for example, when exchangeing data over a slower connection. RULE: The peer that requests a Channel.Flow method MAY disconnect and/or ban a peer that does not respect the request. PARAMETERS: active: boolean start/stop content frames If True, the peer starts sending content frames. If False, the peer stops sending content frames. """ args = AMQPWriter() args.write_bit(active) self._send_method((20, 20), args) return self.wait(allowed_methods=[ (20, 21), # Channel.flow_ok ]) def _flow(self, args): """ enable/disable flow from peer This method asks the peer to pause or restart the flow of content data. This is a simple flow-control mechanism that a peer can use to avoid oveflowing its queues or otherwise finding itself receiving more messages than it can process. Note that this method is not intended for window control. The peer that receives a request to stop sending content should finish sending the current content, if any, and then wait until it receives a Flow restart method. RULE: When a new channel is opened, it is active. Some applications assume that channels are inactive until started. To emulate this behaviour a client MAY open the channel, then pause it. RULE: When sending content data in multiple frames, a peer SHOULD monitor the channel for incoming methods and respond to a Channel.Flow as rapidly as possible. RULE: A peer MAY use the Channel.Flow method to throttle incoming content data for internal reasons, for example, when exchangeing data over a slower connection. RULE: The peer that requests a Channel.Flow method MAY disconnect and/or ban a peer that does not respect the request. PARAMETERS: active: boolean start/stop content frames If True, the peer starts sending content frames. If False, the peer stops sending content frames. """ self.active = args.read_bit() self._x_flow_ok(self.active) def _x_flow_ok(self, active): """ confirm a flow method Confirms to the peer that a flow command was received and processed. PARAMETERS: active: boolean current flow setting Confirms the setting of the processed flow method: True means the peer will start sending or continue to send content frames; False means it will not. """ args = AMQPWriter() args.write_bit(active) self._send_method((20, 21), args) def _flow_ok(self, args): """ confirm a flow method Confirms to the peer that a flow command was received and processed. PARAMETERS: active: boolean current flow setting Confirms the setting of the processed flow method: True means the peer will start sending or continue to send content frames; False means it will not. """ return args.read_bit() def _x_open(self, out_of_band=''): """ open a channel for use This method opens a virtual connection (a channel). RULE: This method MUST NOT be called when the channel is already open. PARAMETERS: out_of_band: shortstr out-of-band settings Configures out-of-band transfers on this channel. The syntax and meaning of this field will be formally defined at a later date. """ if self.is_open: return args = AMQPWriter() args.write_shortstr(out_of_band) self._send_method((20, 10), args) return self.wait(allowed_methods=[ (20, 11), # Channel.open_ok ]) def _open_ok(self, args): """ signal that the channel is ready This method signals to the client that the channel is ready for use. """ self.is_open = True AMQP_LOGGER.debug('Channel open') ############# # # Access # # # work with access tickets # # The protocol control access to server resources using access # tickets. A client must explicitly request access tickets before # doing work. An access ticket grants a client the right to use a # specific set of resources - called a "realm" - in specific ways. # # GRAMMAR: # # access = C:REQUEST S:REQUEST-OK # # def access_request(self, realm, exclusive=False, passive=False, active=False, write=False, read=False): """ request an access ticket This method requests an access ticket for an access realm. The server responds by granting the access ticket. If the client does not have access rights to the requested realm this causes a connection exception. Access tickets are a per-channel resource. RULE: The realm name MUST start with either "/data" (for application resources) or "/admin" (for server administration resources). If the realm starts with any other path, the server MUST raise a connection exception with reply code 403 (access refused). RULE: The server MUST implement the /data realm and MAY implement the /admin realm. The mapping of resources to realms is not defined in the protocol - this is a server- side configuration issue. PARAMETERS: realm: shortstr name of requested realm RULE: If the specified realm is not known to the server, the server must raise a channel exception with reply code 402 (invalid path). exclusive: boolean request exclusive access Request exclusive access to the realm. If the server cannot grant this - because there are other active tickets for the realm - it raises a channel exception. passive: boolean request passive access Request message passive access to the specified access realm. Passive access lets a client get information about resources in the realm but not to make any changes to them. active: boolean request active access Request message active access to the specified access realm. Acvtive access lets a client get create and delete resources in the realm. write: boolean request write access Request write access to the specified access realm. Write access lets a client publish messages to all exchanges in the realm. read: boolean request read access Request read access to the specified access realm. Read access lets a client consume messages from queues in the realm. The most recently requested ticket is used as the channel's default ticket for any method that requires a ticket. """ args = AMQPWriter() args.write_shortstr(realm) args.write_bit(exclusive) args.write_bit(passive) args.write_bit(active) args.write_bit(write) args.write_bit(read) self._send_method((30, 10), args) return self.wait(allowed_methods=[ (30, 11), # Channel.access_request_ok ]) def _access_request_ok(self, args): """ grant access to server resources This method provides the client with an access ticket. The access ticket is valid within the current channel and for the lifespan of the channel. RULE: The client MUST NOT use access tickets except within the same channel as originally granted. RULE: The server MUST isolate access tickets per channel and treat an attempt by a client to mix these as a connection exception. PARAMETERS: ticket: short """ self.default_ticket = args.read_short() return self.default_ticket ############# # # Exchange # # # work with exchanges # # Exchanges match and distribute messages across queues. # Exchanges can be configured in the server or created at runtime. # # GRAMMAR: # # exchange = C:DECLARE S:DECLARE-OK # / C:DELETE S:DELETE-OK # # RULE: # # The server MUST implement the direct and fanout exchange # types, and predeclare the corresponding exchanges named # amq.direct and amq.fanout in each virtual host. The server # MUST also predeclare a direct exchange to act as the default # exchange for content Publish methods and for default queue # bindings. # # RULE: # # The server SHOULD implement the topic exchange type, and # predeclare the corresponding exchange named amq.topic in # each virtual host. # # RULE: # # The server MAY implement the system exchange type, and # predeclare the corresponding exchanges named amq.system in # each virtual host. If the client attempts to bind a queue to # the system exchange, the server MUST raise a connection # exception with reply code 507 (not allowed). # # RULE: # # The default exchange MUST be defined as internal, and be # inaccessible to the client except by specifying an empty # exchange name in a content Publish method. That is, the # server MUST NOT let clients make explicit bindings to this # exchange. # # def exchange_declare(self, exchange, type, passive=False, durable=False, auto_delete=True, internal=False, nowait=False, arguments=None, ticket=None): """ declare exchange, create if needed This method creates an exchange if it does not already exist, and if the exchange exists, verifies that it is of the correct and expected class. RULE: The server SHOULD support a minimum of 16 exchanges per virtual host and ideally, impose no limit except as defined by available resources. PARAMETERS: exchange: shortstr RULE: Exchange names starting with "amq." are reserved for predeclared and standardised exchanges. If the client attempts to create an exchange starting with "amq.", the server MUST raise a channel exception with reply code 403 (access refused). type: shortstr exchange type Each exchange belongs to one of a set of exchange types implemented by the server. The exchange types define the functionality of the exchange - i.e. how messages are routed through it. It is not valid or meaningful to attempt to change the type of an existing exchange. RULE: If the exchange already exists with a different type, the server MUST raise a connection exception with a reply code 507 (not allowed). RULE: If the server does not support the requested exchange type it MUST raise a connection exception with a reply code 503 (command invalid). passive: boolean do not create exchange If set, the server will not create the exchange. The client can use this to check whether an exchange exists without modifying the server state. RULE: If set, and the exchange does not already exist, the server MUST raise a channel exception with reply code 404 (not found). durable: boolean request a durable exchange If set when creating a new exchange, the exchange will be marked as durable. Durable exchanges remain active when a server restarts. Non-durable exchanges (transient exchanges) are purged if/when a server restarts. RULE: The server MUST support both durable and transient exchanges. RULE: The server MUST ignore the durable field if the exchange already exists. auto_delete: boolean auto-delete when unused If set, the exchange is deleted when all queues have finished using it. RULE: The server SHOULD allow for a reasonable delay between the point when it determines that an exchange is not being used (or no longer used), and the point when it deletes the exchange. At the least it must allow a client to create an exchange and then bind a queue to it, with a small but non-zero delay between these two actions. RULE: The server MUST ignore the auto-delete field if the exchange already exists. internal: boolean create internal exchange If set, the exchange may not be used directly by publishers, but only when bound to other exchanges. Internal exchanges are used to construct wiring that is not visible to applications. nowait: boolean do not send a reply method If set, the server will not respond to the method. The client should not wait for a reply method. If the server could not complete the method it will raise a channel or connection exception. arguments: table arguments for declaration A set of arguments for the declaration. The syntax and semantics of these arguments depends on the server implementation. This field is ignored if passive is True. ticket: short When a client defines a new exchange, this belongs to the access realm of the ticket used. All further work done with that exchange must be done with an access ticket for the same realm. RULE: The client MUST provide a valid access ticket giving "active" access to the realm in which the exchange exists or will be created, or "passive" access if the if-exists flag is set. """ if arguments is None: arguments = {} args = AMQPWriter() if ticket is not None: args.write_short(ticket) else: args.write_short(self.default_ticket) args.write_shortstr(exchange) args.write_shortstr(type) args.write_bit(passive) args.write_bit(durable) args.write_bit(auto_delete) args.write_bit(internal) args.write_bit(nowait) args.write_table(arguments) self._send_method((40, 10), args) if not nowait: return self.wait(allowed_methods=[ (40, 11), # Channel.exchange_declare_ok ]) def _exchange_declare_ok(self, args): """ confirms an exchange declaration This method confirms a Declare method and confirms the name of the exchange, essential for automatically-named exchanges. """ pass def exchange_delete(self, exchange, if_unused=False, nowait=False, ticket=None): """ delete an exchange This method deletes an exchange. When an exchange is deleted all queue bindings on the exchange are cancelled. PARAMETERS: exchange: shortstr RULE: The exchange MUST exist. Attempting to delete a non-existing exchange causes a channel exception. if_unused: boolean delete only if unused If set, the server will only delete the exchange if it has no queue bindings. If the exchange has queue bindings the server does not delete it but raises a channel exception instead. RULE: If set, the server SHOULD delete the exchange but only if it has no queue bindings. RULE: If set, the server SHOULD raise a channel exception if the exchange is in use. nowait: boolean do not send a reply method If set, the server will not respond to the method. The client should not wait for a reply method. If the server could not complete the method it will raise a channel or connection exception. ticket: short RULE: The client MUST provide a valid access ticket giving "active" access rights to the exchange's access realm. """ args = AMQPWriter() if ticket is not None: args.write_short(ticket) else: args.write_short(self.default_ticket) args.write_shortstr(exchange) args.write_bit(if_unused) args.write_bit(nowait) self._send_method((40, 20), args) if not nowait: return self.wait(allowed_methods=[ (40, 21), # Channel.exchange_delete_ok ]) def _exchange_delete_ok(self, args): """ confirm deletion of an exchange This method confirms the deletion of an exchange. """ pass ############# # # Queue # # # work with queues # # Queues store and forward messages. Queues can be configured in # the server or created at runtime. Queues must be attached to at # least one exchange in order to receive messages from publishers. # # GRAMMAR: # # queue = C:DECLARE S:DECLARE-OK # / C:BIND S:BIND-OK # / C:PURGE S:PURGE-OK # / C:DELETE S:DELETE-OK # # RULE: # # A server MUST allow any content class to be sent to any # queue, in any mix, and queue and delivery these content # classes independently. Note that all methods that fetch # content off queues are specific to a given content class. # # def queue_bind(self, queue, exchange, routing_key='', nowait=False, arguments=None, ticket=None): """ bind queue to an exchange This method binds a queue to an exchange. Until a queue is bound it will not receive any messages. In a classic messaging model, store-and-forward queues are bound to a dest exchange and subscription queues are bound to a dest_wild exchange. RULE: A server MUST allow ignore duplicate bindings - that is, two or more bind methods for a specific queue, with identical arguments - without treating these as an error. RULE: If a bind fails, the server MUST raise a connection exception. RULE: The server MUST NOT allow a durable queue to bind to a transient exchange. If the client attempts this the server MUST raise a channel exception. RULE: Bindings for durable queues are automatically durable and the server SHOULD restore such bindings after a server restart. RULE: If the client attempts to an exchange that was declared as internal, the server MUST raise a connection exception with reply code 530 (not allowed). RULE: The server SHOULD support at least 4 bindings per queue, and ideally, impose no limit except as defined by available resources. PARAMETERS: queue: shortstr Specifies the name of the queue to bind. If the queue name is empty, refers to the current queue for the channel, which is the last declared queue. RULE: If the client did not previously declare a queue, and the queue name in this method is empty, the server MUST raise a connection exception with reply code 530 (not allowed). RULE: If the queue does not exist the server MUST raise a channel exception with reply code 404 (not found). exchange: shortstr The name of the exchange to bind to. RULE: If the exchange does not exist the server MUST raise a channel exception with reply code 404 (not found). routing_key: shortstr message routing key Specifies the routing key for the binding. The routing key is used for routing messages depending on the exchange configuration. Not all exchanges use a routing key - refer to the specific exchange documentation. If the routing key is empty and the queue name is empty, the routing key will be the current queue for the channel, which is the last declared queue. nowait: boolean do not send a reply method If set, the server will not respond to the method. The client should not wait for a reply method. If the server could not complete the method it will raise a channel or connection exception. arguments: table arguments for binding A set of arguments for the binding. The syntax and semantics of these arguments depends on the exchange class. ticket: short The client provides a valid access ticket giving "active" access rights to the queue's access realm. """ if arguments is None: arguments = {} args = AMQPWriter() if ticket is not None: args.write_short(ticket) else: args.write_short(self.default_ticket) args.write_shortstr(queue) args.write_shortstr(exchange) args.write_shortstr(routing_key) args.write_bit(nowait) args.write_table(arguments) self._send_method((50, 20), args) if not nowait: return self.wait(allowed_methods=[ (50, 21), # Channel.queue_bind_ok ]) def _queue_bind_ok(self, args): """ confirm bind successful This method confirms that the bind was successful. """ pass def queue_unbind(self, queue, exchange, routing_key='', nowait=False, arguments=None, ticket=None): """ NOTE::::This is not part of AMQP 0-8, but RabbitMQ supports this as an extension unbind a queue from an exchange This method unbinds a queue from an exchange. RULE: If a unbind fails, the server MUST raise a connection exception. PARAMETERS: queue: shortstr Specifies the name of the queue to unbind. RULE: The client MUST either specify a queue name or have previously declared a queue on the same channel RULE: The client MUST NOT attempt to unbind a queue that does not exist. exchange: shortstr The name of the exchange to unbind from. RULE: The client MUST NOT attempt to unbind a queue from an exchange that does not exist. RULE: The server MUST accept a blank exchange name to mean the default exchange. routing_key: shortstr routing key of binding Specifies the routing key of the binding to unbind. arguments: table arguments of binding Specifies the arguments of the binding to unbind. """ if arguments is None: arguments = {} args = AMQPWriter() if ticket is not None: args.write_short(ticket) else: args.write_short(self.default_ticket) args.write_shortstr(queue) args.write_shortstr(exchange) args.write_shortstr(routing_key) #args.write_bit(nowait) args.write_table(arguments) self._send_method((50, 50), args) if not nowait: return self.wait(allowed_methods=[ (50, 51), # Channel.queue_unbind_ok ]) def _queue_unbind_ok(self, args): """ confirm unbind successful This method confirms that the unbind was successful. """ pass def queue_declare(self, queue='', passive=False, durable=False, exclusive=False, auto_delete=True, nowait=False, arguments=None, ticket=None): """ declare queue, create if needed This method creates or checks a queue. When creating a new queue the client can specify various properties that control the durability of the queue and its contents, and the level of sharing for the queue. RULE: The server MUST create a default binding for a newly- created queue to the default exchange, which is an exchange of type 'direct'. RULE: The server SHOULD support a minimum of 256 queues per virtual host and ideally, impose no limit except as defined by available resources. PARAMETERS: queue: shortstr RULE: The queue name MAY be empty, in which case the server MUST create a new queue with a unique generated name and return this to the client in the Declare-Ok method. RULE: Queue names starting with "amq." are reserved for predeclared and standardised server queues. If the queue name starts with "amq." and the passive option is False, the server MUST raise a connection exception with reply code 403 (access refused). passive: boolean do not create queue If set, the server will not create the queue. The client can use this to check whether a queue exists without modifying the server state. RULE: If set, and the queue does not already exist, the server MUST respond with a reply code 404 (not found) and raise a channel exception. durable: boolean request a durable queue If set when creating a new queue, the queue will be marked as durable. Durable queues remain active when a server restarts. Non-durable queues (transient queues) are purged if/when a server restarts. Note that durable queues do not necessarily hold persistent messages, although it does not make sense to send persistent messages to a transient queue. RULE: The server MUST recreate the durable queue after a restart. RULE: The server MUST support both durable and transient queues. RULE: The server MUST ignore the durable field if the queue already exists. exclusive: boolean request an exclusive queue Exclusive queues may only be consumed from by the current connection. Setting the 'exclusive' flag always implies 'auto-delete'. RULE: The server MUST support both exclusive (private) and non-exclusive (shared) queues. RULE: The server MUST raise a channel exception if 'exclusive' is specified and the queue already exists and is owned by a different connection. auto_delete: boolean auto-delete queue when unused If set, the queue is deleted when all consumers have finished using it. Last consumer can be cancelled either explicitly or because its channel is closed. If there was no consumer ever on the queue, it won't be deleted. RULE: The server SHOULD allow for a reasonable delay between the point when it determines that a queue is not being used (or no longer used), and the point when it deletes the queue. At the least it must allow a client to create a queue and then create a consumer to read from it, with a small but non-zero delay between these two actions. The server should equally allow for clients that may be disconnected prematurely, and wish to re- consume from the same queue without losing messages. We would recommend a configurable timeout, with a suitable default value being one minute. RULE: The server MUST ignore the auto-delete field if the queue already exists. nowait: boolean do not send a reply method If set, the server will not respond to the method. The client should not wait for a reply method. If the server could not complete the method it will raise a channel or connection exception. arguments: table arguments for declaration A set of arguments for the declaration. The syntax and semantics of these arguments depends on the server implementation. This field is ignored if passive is True. ticket: short When a client defines a new queue, this belongs to the access realm of the ticket used. All further work done with that queue must be done with an access ticket for the same realm. The client provides a valid access ticket giving "active" access to the realm in which the queue exists or will be created, or "passive" access if the if- exists flag is set. Returns a tuple containing 3 items: the name of the queue (essential for automatically-named queues) message count consumer count """ if arguments is None: arguments = {} args = AMQPWriter() if ticket is not None: args.write_short(ticket) else: args.write_short(self.default_ticket) args.write_shortstr(queue) args.write_bit(passive) args.write_bit(durable) args.write_bit(exclusive) args.write_bit(auto_delete) args.write_bit(nowait) args.write_table(arguments) self._send_method((50, 10), args) if not nowait: return self.wait(allowed_methods=[ (50, 11), # Channel.queue_declare_ok ]) def _queue_declare_ok(self, args): """ confirms a queue definition This method confirms a Declare method and confirms the name of the queue, essential for automatically-named queues. PARAMETERS: queue: shortstr Reports the name of the queue. If the server generated a queue name, this field contains that name. message_count: long number of messages in queue Reports the number of messages in the queue, which will be zero for newly-created queues. consumer_count: long number of consumers Reports the number of active consumers for the queue. Note that consumers can suspend activity (Channel.Flow) in which case they do not appear in this count. """ queue = args.read_shortstr() message_count = args.read_long() consumer_count = args.read_long() return queue, message_count, consumer_count def queue_delete(self, queue='', if_unused=False, if_empty=False, nowait=False, ticket=None): """ delete a queue This method deletes a queue. When a queue is deleted any pending messages are sent to a dead-letter queue if this is defined in the server configuration, and all consumers on the queue are cancelled. RULE: The server SHOULD use a dead-letter queue to hold messages that were pending on a deleted queue, and MAY provide facilities for a system administrator to move these messages back to an active queue. PARAMETERS: queue: shortstr Specifies the name of the queue to delete. If the queue name is empty, refers to the current queue for the channel, which is the last declared queue. RULE: If the client did not previously declare a queue, and the queue name in this method is empty, the server MUST raise a connection exception with reply code 530 (not allowed). RULE: The queue must exist. Attempting to delete a non- existing queue causes a channel exception. if_unused: boolean delete only if unused If set, the server will only delete the queue if it has no consumers. If the queue has consumers the server does does not delete it but raises a channel exception instead. RULE: The server MUST respect the if-unused flag when deleting a queue. if_empty: boolean delete only if empty If set, the server will only delete the queue if it has no messages. If the queue is not empty the server raises a channel exception. nowait: boolean do not send a reply method If set, the server will not respond to the method. The client should not wait for a reply method. If the server could not complete the method it will raise a channel or connection exception. ticket: short The client provides a valid access ticket giving "active" access rights to the queue's access realm. """ args = AMQPWriter() if ticket is not None: args.write_short(ticket) else: args.write_short(self.default_ticket) args.write_shortstr(queue) args.write_bit(if_unused) args.write_bit(if_empty) args.write_bit(nowait) self._send_method((50, 40), args) if not nowait: return self.wait(allowed_methods=[ (50, 41), # Channel.queue_delete_ok ]) def _queue_delete_ok(self, args): """ confirm deletion of a queue This method confirms the deletion of a queue. PARAMETERS: message_count: long number of messages purged Reports the number of messages purged. """ return args.read_long() def queue_purge(self, queue='', nowait=False, ticket=None): """ purge a queue This method removes all messages from a queue. It does not cancel consumers. Purged messages are deleted without any formal "undo" mechanism. RULE: A call to purge MUST result in an empty queue. RULE: On transacted channels the server MUST not purge messages that have already been sent to a client but not yet acknowledged. RULE: The server MAY implement a purge queue or log that allows system administrators to recover accidentally-purged messages. The server SHOULD NOT keep purged messages in the same storage spaces as the live messages since the volumes of purged messages may get very large. PARAMETERS: queue: shortstr Specifies the name of the queue to purge. If the queue name is empty, refers to the current queue for the channel, which is the last declared queue. RULE: If the client did not previously declare a queue, and the queue name in this method is empty, the server MUST raise a connection exception with reply code 530 (not allowed). RULE: The queue must exist. Attempting to purge a non- existing queue causes a channel exception. nowait: boolean do not send a reply method If set, the server will not respond to the method. The client should not wait for a reply method. If the server could not complete the method it will raise a channel or connection exception. ticket: short The access ticket must be for the access realm that holds the queue. RULE: The client MUST provide a valid access ticket giving "read" access rights to the queue's access realm. Note that purging a queue is equivalent to reading all messages and discarding them. if nowait is False, returns a message_count """ args = AMQPWriter() if ticket is not None: args.write_short(ticket) else: args.write_short(self.default_ticket) args.write_shortstr(queue) args.write_bit(nowait) self._send_method((50, 30), args) if not nowait: return self.wait(allowed_methods=[ (50, 31), # Channel.queue_purge_ok ]) def _queue_purge_ok(self, args): """ confirms a queue purge This method confirms the purge of a queue. PARAMETERS: message_count: long number of messages purged Reports the number of messages purged. """ return args.read_long() ############# # # Basic # # # work with basic content # # The Basic class provides methods that support an industry- # standard messaging model. # # GRAMMAR: # # basic = C:QOS S:QOS-OK # / C:CONSUME S:CONSUME-OK # / C:CANCEL S:CANCEL-OK # / C:PUBLISH content # / S:RETURN content # / S:DELIVER content # / C:GET ( S:GET-OK content / S:GET-EMPTY ) # / C:ACK # / C:REJECT # # RULE: # # The server SHOULD respect the persistent property of basic # messages and SHOULD make a best-effort to hold persistent # basic messages on a reliable storage mechanism. # # RULE: # # The server MUST NOT discard a persistent basic message in # case of a queue overflow. The server MAY use the # Channel.Flow method to slow or stop a basic message # publisher when necessary. # # RULE: # # The server MAY overflow non-persistent basic messages to # persistent storage and MAY discard or dead-letter non- # persistent basic messages on a priority basis if the queue # size exceeds some configured limit. # # RULE: # # The server MUST implement at least 2 priority levels for # basic messages, where priorities 0-4 and 5-9 are treated as # two distinct levels. The server MAY implement up to 10 # priority levels. # # RULE: # # The server MUST deliver messages of the same priority in # order irrespective of their individual persistence. # # RULE: # # The server MUST support both automatic and explicit # acknowledgements on Basic content. # def basic_ack(self, delivery_tag, multiple=False): """ acknowledge one or more messages This method acknowledges one or more messages delivered via the Deliver or Get-Ok methods. The client can ask to confirm a single message or a set of messages up to and including a specific message. PARAMETERS: delivery_tag: longlong server-assigned delivery tag The server-assigned and channel-specific delivery tag RULE: The delivery tag is valid only within the channel from which the message was received. I.e. a client MUST NOT receive a message on one channel and then acknowledge it on another. RULE: The server MUST NOT use a zero value for delivery tags. Zero is reserved for client use, meaning "all messages so far received". multiple: boolean acknowledge multiple messages If set to True, the delivery tag is treated as "up to and including", so that the client can acknowledge multiple messages with a single method. If set to False, the delivery tag refers to a single message. If the multiple field is True, and the delivery tag is zero, tells the server to acknowledge all outstanding mesages. RULE: The server MUST validate that a non-zero delivery- tag refers to an delivered message, and raise a channel exception if this is not the case. """ args = AMQPWriter() args.write_longlong(delivery_tag) args.write_bit(multiple) self._send_method((60, 80), args) def basic_cancel(self, consumer_tag, nowait=False): """ end a queue consumer This method cancels a consumer. This does not affect already delivered messages, but it does mean the server will not send any more messages for that consumer. The client may receive an abitrary number of messages in between sending the cancel method and receiving the cancel-ok reply. RULE: If the queue no longer exists when the client sends a cancel command, or the consumer has been cancelled for other reasons, this command has no effect. PARAMETERS: consumer_tag: shortstr consumer tag Identifier for the consumer, valid within the current connection. RULE: The consumer tag is valid only within the channel from which the consumer was created. I.e. a client MUST NOT create a consumer in one channel and then use it in another. nowait: boolean do not send a reply method If set, the server will not respond to the method. The client should not wait for a reply method. If the server could not complete the method it will raise a channel or connection exception. """ args = AMQPWriter() args.write_shortstr(consumer_tag) args.write_bit(nowait) self._send_method((60, 30), args) return self.wait(allowed_methods=[ (60, 31), # Channel.basic_cancel_ok ]) def _basic_cancel_ok(self, args): """ confirm a cancelled consumer This method confirms that the cancellation was completed. PARAMETERS: consumer_tag: shortstr consumer tag Identifier for the consumer, valid within the current connection. RULE: The consumer tag is valid only within the channel from which the consumer was created. I.e. a client MUST NOT create a consumer in one channel and then use it in another. """ consumer_tag = args.read_shortstr() del self.callbacks[consumer_tag] def basic_consume(self, queue='', consumer_tag='', no_local=False, no_ack=False, exclusive=False, nowait=False, callback=None, ticket=None): """ start a queue consumer This method asks the server to start a "consumer", which is a transient request for messages from a specific queue. Consumers last as long as the channel they were created on, or until the client cancels them. RULE: The server SHOULD support at least 16 consumers per queue, unless the queue was declared as private, and ideally, impose no limit except as defined by available resources. PARAMETERS: queue: shortstr Specifies the name of the queue to consume from. If the queue name is null, refers to the current queue for the channel, which is the last declared queue. RULE: If the client did not previously declare a queue, and the queue name in this method is empty, the server MUST raise a connection exception with reply code 530 (not allowed). consumer_tag: shortstr Specifies the identifier for the consumer. The consumer tag is local to a connection, so two clients can use the same consumer tags. If this field is empty the server will generate a unique tag. RULE: The tag MUST NOT refer to an existing consumer. If the client attempts to create two consumers with the same non-empty tag the server MUST raise a connection exception with reply code 530 (not allowed). no_local: boolean do not deliver own messages If the no-local field is set the server will not send messages to the client that published them. no_ack: boolean no acknowledgement needed If this field is set the server does not expect acknowledgments for messages. That is, when a message is delivered to the client the server automatically and silently acknowledges it on behalf of the client. This functionality increases performance but at the cost of reliability. Messages can get lost if a client dies before it can deliver them to the application. exclusive: boolean request exclusive access Request exclusive consumer access, meaning only this consumer can access the queue. RULE: If the server cannot grant exclusive access to the queue when asked, - because there are other consumers active - it MUST raise a channel exception with return code 403 (access refused). nowait: boolean do not send a reply method If set, the server will not respond to the method. The client should not wait for a reply method. If the server could not complete the method it will raise a channel or connection exception. callback: Python callable function/method called with each delivered message For each message delivered by the broker, the callable will be called with a Message object as the single argument. If no callable is specified, messages are quietly discarded, no_ack should probably be set to True in that case. ticket: short RULE: The client MUST provide a valid access ticket giving "read" access rights to the realm for the queue. """ args = AMQPWriter() if ticket is not None: args.write_short(ticket) else: args.write_short(self.default_ticket) args.write_shortstr(queue) args.write_shortstr(consumer_tag) args.write_bit(no_local) args.write_bit(no_ack) args.write_bit(exclusive) args.write_bit(nowait) self._send_method((60, 20), args) if not nowait: consumer_tag = self.wait(allowed_methods=[ (60, 21), # Channel.basic_consume_ok ]) self.callbacks[consumer_tag] = callback return consumer_tag def _basic_consume_ok(self, args): """ confirm a new consumer The server provides the client with a consumer tag, which is used by the client for methods called on the consumer at a later stage. PARAMETERS: consumer_tag: shortstr Holds the consumer tag specified by the client or provided by the server. """ return args.read_shortstr() def _basic_deliver(self, args, msg): """ notify the client of a consumer message This method delivers a message to the client, via a consumer. In the asynchronous message delivery model, the client starts a consumer using the Consume method, then the server responds with Deliver methods as and when messages arrive for that consumer. RULE: The server SHOULD track the number of times a message has been delivered to clients and when a message is redelivered a certain number of times - e.g. 5 times - without being acknowledged, the server SHOULD consider the message to be unprocessable (possibly causing client applications to abort), and move the message to a dead letter queue. PARAMETERS: consumer_tag: shortstr consumer tag Identifier for the consumer, valid within the current connection. RULE: The consumer tag is valid only within the channel from which the consumer was created. I.e. a client MUST NOT create a consumer in one channel and then use it in another. delivery_tag: longlong server-assigned delivery tag The server-assigned and channel-specific delivery tag RULE: The delivery tag is valid only within the channel from which the message was received. I.e. a client MUST NOT receive a message on one channel and then acknowledge it on another. RULE: The server MUST NOT use a zero value for delivery tags. Zero is reserved for client use, meaning "all messages so far received". redelivered: boolean message is being redelivered This indicates that the message has been previously delivered to this or another client. exchange: shortstr Specifies the name of the exchange that the message was originally published to. routing_key: shortstr Message routing key Specifies the routing key name specified when the message was published. """ consumer_tag = args.read_shortstr() delivery_tag = args.read_longlong() redelivered = args.read_bit() exchange = args.read_shortstr() routing_key = args.read_shortstr() msg.delivery_info = { 'channel': self, 'consumer_tag': consumer_tag, 'delivery_tag': delivery_tag, 'redelivered': redelivered, 'exchange': exchange, 'routing_key': routing_key, } func = self.callbacks.get(consumer_tag, None) if func is not None: func(msg) def basic_get(self, queue='', no_ack=False, ticket=None): """ direct access to a queue This method provides a direct access to the messages in a queue using a synchronous dialogue that is designed for specific types of application where synchronous functionality is more important than performance. PARAMETERS: queue: shortstr Specifies the name of the queue to consume from. If the queue name is null, refers to the current queue for the channel, which is the last declared queue. RULE: If the client did not previously declare a queue, and the queue name in this method is empty, the server MUST raise a connection exception with reply code 530 (not allowed). no_ack: boolean no acknowledgement needed If this field is set the server does not expect acknowledgments for messages. That is, when a message is delivered to the client the server automatically and silently acknowledges it on behalf of the client. This functionality increases performance but at the cost of reliability. Messages can get lost if a client dies before it can deliver them to the application. ticket: short RULE: The client MUST provide a valid access ticket giving "read" access rights to the realm for the queue. Non-blocking, returns a message object, or None. """ args = AMQPWriter() if ticket is not None: args.write_short(ticket) else: args.write_short(self.default_ticket) args.write_shortstr(queue) args.write_bit(no_ack) self._send_method((60, 70), args) return self.wait(allowed_methods=[ (60, 71), # Channel.basic_get_ok (60, 72), # Channel.basic_get_empty ]) def _basic_get_empty(self, args): """ indicate no messages available This method tells the client that the queue has no messages available for the client. PARAMETERS: cluster_id: shortstr Cluster id For use by cluster applications, should not be used by client applications. """ cluster_id = args.read_shortstr() def _basic_get_ok(self, args, msg): """ provide client with a message This method delivers a message to the client following a get method. A message delivered by 'get-ok' must be acknowledged unless the no-ack option was set in the get method. PARAMETERS: delivery_tag: longlong server-assigned delivery tag The server-assigned and channel-specific delivery tag RULE: The delivery tag is valid only within the channel from which the message was received. I.e. a client MUST NOT receive a message on one channel and then acknowledge it on another. RULE: The server MUST NOT use a zero value for delivery tags. Zero is reserved for client use, meaning "all messages so far received". redelivered: boolean message is being redelivered This indicates that the message has been previously delivered to this or another client. exchange: shortstr Specifies the name of the exchange that the message was originally published to. If empty, the message was published to the default exchange. routing_key: shortstr Message routing key Specifies the routing key name specified when the message was published. message_count: long number of messages pending This field reports the number of messages pending on the queue, excluding the message being delivered. Note that this figure is indicative, not reliable, and can change arbitrarily as messages are added to the queue and removed by other clients. """ delivery_tag = args.read_longlong() redelivered = args.read_bit() exchange = args.read_shortstr() routing_key = args.read_shortstr() message_count = args.read_long() msg.delivery_info = { 'delivery_tag': delivery_tag, 'redelivered': redelivered, 'exchange': exchange, 'routing_key': routing_key, 'message_count': message_count } return msg def basic_publish(self, msg, exchange='', routing_key='', mandatory=False, immediate=False, ticket=None): """ publish a message This method publishes a message to a specific exchange. The message will be routed to queues as defined by the exchange configuration and distributed to any active consumers when the transaction, if any, is committed. PARAMETERS: exchange: shortstr Specifies the name of the exchange to publish to. The exchange name can be empty, meaning the default exchange. If the exchange name is specified, and that exchange does not exist, the server will raise a channel exception. RULE: The server MUST accept a blank exchange name to mean the default exchange. RULE: If the exchange was declared as an internal exchange, the server MUST raise a channel exception with a reply code 403 (access refused). RULE: The exchange MAY refuse basic content in which case it MUST raise a channel exception with reply code 540 (not implemented). routing_key: shortstr Message routing key Specifies the routing key for the message. The routing key is used for routing messages depending on the exchange configuration. mandatory: boolean indicate mandatory routing This flag tells the server how to react if the message cannot be routed to a queue. If this flag is True, the server will return an unroutable message with a Return method. If this flag is False, the server silently drops the message. RULE: The server SHOULD implement the mandatory flag. immediate: boolean request immediate delivery This flag tells the server how to react if the message cannot be routed to a queue consumer immediately. If this flag is set, the server will return an undeliverable message with a Return method. If this flag is zero, the server will queue the message, but with no guarantee that it will ever be consumed. RULE: The server SHOULD implement the immediate flag. ticket: short RULE: The client MUST provide a valid access ticket giving "write" access rights to the access realm for the exchange. """ args = AMQPWriter() if ticket is not None: args.write_short(ticket) else: args.write_short(self.default_ticket) args.write_shortstr(exchange) args.write_shortstr(routing_key) args.write_bit(mandatory) args.write_bit(immediate) self._send_method((60, 40), args, msg) def basic_qos(self, prefetch_size, prefetch_count, a_global): """ specify quality of service This method requests a specific quality of service. The QoS can be specified for the current channel or for all channels on the connection. The particular properties and semantics of a qos method always depend on the content class semantics. Though the qos method could in principle apply to both peers, it is currently meaningful only for the server. PARAMETERS: prefetch_size: long prefetch window in octets The client can request that messages be sent in advance so that when the client finishes processing a message, the following message is already held locally, rather than needing to be sent down the channel. Prefetching gives a performance improvement. This field specifies the prefetch window size in octets. The server will send a message in advance if it is equal to or smaller in size than the available prefetch size (and also falls into other prefetch limits). May be set to zero, meaning "no specific limit", although other prefetch limits may still apply. The prefetch-size is ignored if the no-ack option is set. RULE: The server MUST ignore this setting when the client is not processing any messages - i.e. the prefetch size does not limit the transfer of single messages to a client, only the sending in advance of more messages while the client still has one or more unacknowledged messages. prefetch_count: short prefetch window in messages Specifies a prefetch window in terms of whole messages. This field may be used in combination with the prefetch-size field; a message will only be sent in advance if both prefetch windows (and those at the channel and connection level) allow it. The prefetch- count is ignored if the no-ack option is set. RULE: The server MAY send less data in advance than allowed by the client's specified prefetch windows but it MUST NOT send more. a_global: boolean apply to entire connection By default the QoS settings apply to the current channel only. If this field is set, they are applied to the entire connection. """ args = AMQPWriter() args.write_long(prefetch_size) args.write_short(prefetch_count) args.write_bit(a_global) self._send_method((60, 10), args) return self.wait(allowed_methods=[ (60, 11), # Channel.basic_qos_ok ]) def _basic_qos_ok(self, args): """ confirm the requested qos This method tells the client that the requested QoS levels could be handled by the server. The requested QoS applies to all active consumers until a new QoS is defined. """ pass def basic_recover(self, requeue=False): """ redeliver unacknowledged messages This method asks the broker to redeliver all unacknowledged messages on a specified channel. Zero or more messages may be redelivered. This method is only allowed on non-transacted channels. RULE: The server MUST set the redelivered flag on all messages that are resent. RULE: The server MUST raise a channel exception if this is called on a transacted channel. PARAMETERS: requeue: boolean requeue the message If this field is False, the message will be redelivered to the original recipient. If this field is True, the server will attempt to requeue the message, potentially then delivering it to an alternative subscriber. """ args = AMQPWriter() args.write_bit(requeue) self._send_method((60, 100), args) def basic_reject(self, delivery_tag, requeue): """ reject an incoming message This method allows a client to reject a message. It can be used to interrupt and cancel large incoming messages, or return untreatable messages to their original queue. RULE: The server SHOULD be capable of accepting and process the Reject method while sending message content with a Deliver or Get-Ok method. I.e. the server should read and process incoming methods while sending output frames. To cancel a partially-send content, the server sends a content body frame of size 1 (i.e. with no data except the frame-end octet). RULE: The server SHOULD interpret this method as meaning that the client is unable to process the message at this time. RULE: A client MUST NOT use this method as a means of selecting messages to process. A rejected message MAY be discarded or dead-lettered, not necessarily passed to another client. PARAMETERS: delivery_tag: longlong server-assigned delivery tag The server-assigned and channel-specific delivery tag RULE: The delivery tag is valid only within the channel from which the message was received. I.e. a client MUST NOT receive a message on one channel and then acknowledge it on another. RULE: The server MUST NOT use a zero value for delivery tags. Zero is reserved for client use, meaning "all messages so far received". requeue: boolean requeue the message If this field is False, the message will be discarded. If this field is True, the server will attempt to requeue the message. RULE: The server MUST NOT deliver the message to the same client within the context of the current channel. The recommended strategy is to attempt to deliver the message to an alternative consumer, and if that is not possible, to move the message to a dead-letter queue. The server MAY use more sophisticated tracking to hold the message on the queue and redeliver it to the same client at a later stage. """ args = AMQPWriter() args.write_longlong(delivery_tag) args.write_bit(requeue) self._send_method((60, 90), args) def _basic_return(self, args, msg): """ return a failed message This method returns an undeliverable message that was published with the "immediate" flag set, or an unroutable message published with the "mandatory" flag set. The reply code and text provide information about the reason that the message was undeliverable. PARAMETERS: reply_code: short The reply code. The AMQ reply codes are defined in AMQ RFC 011. reply_text: shortstr The localised reply text. This text can be logged as an aid to resolving issues. exchange: shortstr Specifies the name of the exchange that the message was originally published to. routing_key: shortstr Message routing key Specifies the routing key name specified when the message was published. """ reply_code = args.read_short() reply_text = args.read_shortstr() exchange = args.read_shortstr() routing_key = args.read_shortstr() self.returned_messages.put( (reply_code, reply_text, exchange, routing_key, msg) ) ############# # # Tx # # # work with standard transactions # # Standard transactions provide so-called "1.5 phase commit". We # can ensure that work is never lost, but there is a chance of # confirmations being lost, so that messages may be resent. # Applications that use standard transactions must be able to # detect and ignore duplicate messages. # # GRAMMAR: # # tx = C:SELECT S:SELECT-OK # / C:COMMIT S:COMMIT-OK # / C:ROLLBACK S:ROLLBACK-OK # # RULE: # # An client using standard transactions SHOULD be able to # track all messages received within a reasonable period, and # thus detect and reject duplicates of the same message. It # SHOULD NOT pass these to the application layer. # # def tx_commit(self): """ commit the current transaction This method commits all messages published and acknowledged in the current transaction. A new transaction starts immediately after a commit. """ self._send_method((90, 20)) return self.wait(allowed_methods=[ (90, 21), # Channel.tx_commit_ok ]) def _tx_commit_ok(self, args): """ confirm a successful commit This method confirms to the client that the commit succeeded. Note that if a commit fails, the server raises a channel exception. """ pass def tx_rollback(self): """ abandon the current transaction This method abandons all messages published and acknowledged in the current transaction. A new transaction starts immediately after a rollback. """ self._send_method((90, 30)) return self.wait(allowed_methods=[ (90, 31), # Channel.tx_rollback_ok ]) def _tx_rollback_ok(self, args): """ confirm a successful rollback This method confirms to the client that the rollback succeeded. Note that if an rollback fails, the server raises a channel exception. """ pass def tx_select(self): """ select standard transaction mode This method sets the channel to use standard transactions. The client must use this method at least once on a channel before using the Commit or Rollback methods. """ self._send_method((90, 10)) return self.wait(allowed_methods=[ (90, 11), # Channel.tx_select_ok ]) def _tx_select_ok(self, args): """ confirm transaction mode This method confirms to the client that the channel was successfully set to use standard transactions. """ pass _METHOD_MAP = { (20, 11): _open_ok, (20, 20): _flow, (20, 21): _flow_ok, (20, 30): _alert, (20, 40): _close, (20, 41): _close_ok, (30, 11): _access_request_ok, (40, 11): _exchange_declare_ok, (40, 21): _exchange_delete_ok, (50, 11): _queue_declare_ok, (50, 21): _queue_bind_ok, (50, 31): _queue_purge_ok, (50, 41): _queue_delete_ok, (50, 51): _queue_unbind_ok, (60, 11): _basic_qos_ok, (60, 21): _basic_consume_ok, (60, 31): _basic_cancel_ok, (60, 50): _basic_return, (60, 60): _basic_deliver, (60, 71): _basic_get_ok, (60, 72): _basic_get_empty, (90, 11): _tx_select_ok, (90, 21): _tx_commit_ok, (90, 31): _tx_rollback_ok, } _IMMEDIATE_METHODS = [ (60, 50), # basic_return ] amqplib-1.0.2/amqplib/client_0_8/transport.py0000644000175100017510000001626511640715533021757 0ustar barrypbarryp00000000000000""" Read/Write AMQP frames over network transports. 2009-01-14 Barry Pederson """ # Copyright (C) 2009 Barry Pederson # # 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 re import socket # # See if Python 2.6+ SSL support is available # try: import ssl HAVE_PY26_SSL = True except: HAVE_PY26_SSL = False try: bytes except: # Python 2.5 and lower bytes = str from struct import pack, unpack AMQP_PORT = 5672 # Yes, Advanced Message Queuing Protocol Protocol is redundant AMQP_PROTOCOL_HEADER = 'AMQP\x01\x01\x09\x01'.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 """ def __init__(self, host, connect_timeout): msg = 'socket.getaddrinfo() for %s returned an empty list' % host 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 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, socket.SOL_TCP): af, socktype, proto, canonname, sa = res try: self.sock = socket.socket(af, socktype, proto) self.sock.settimeout(connect_timeout) self.sock.connect(sa) except socket.error, msg: self.sock.close() self.sock = None continue break if not self.sock: # Didn't connect, return the most recent error message raise socket.error, msg self.sock.settimeout(None) self.sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) self._setup_transport() self._write(AMQP_PROTOCOL_HEADER) def __del__(self): self.close() def _read(self, n): """ 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 def read_frame(self): """ Read an AMQP frame. """ frame_type, channel, size = unpack('>BHI', self._read(7)) payload = self._read(size) ch = ord(self._read(1)) if ch == 206: # '\xce' return frame_type, channel, payload else: raise Exception('Framing Error, received 0x%02x while expecting 0xce' % ch) def write_frame(self, frame_type, channel, payload): """ Write out an AMQP frame. """ size = len(payload) self._write(pack('>BHI%dsB' % size, frame_type, channel, size, payload, 0xce)) class SSLTransport(_AbstractTransport): """ Transport that works over SSL """ def __init__(self, host, connect_timeout, ssl): if isinstance(ssl, dict): self.sslopts = ssl self.sslobj = None super(SSLTransport, self).__init__(host, connect_timeout) def _setup_transport(self): """ Wrap the socket in an SSL object, either the new Python 2.6 version, or the older Python 2.5 and lower version. """ if HAVE_PY26_SSL: if hasattr(self, 'sslopts'): self.sslobj = ssl.wrap_socket(self.sock, **self.sslopts) else: self.sslobj = ssl.wrap_socket(self.sock) self.sslobj.do_handshake() else: self.sslobj = socket.ssl(self.sock) def _shutdown_transport(self): """ Unwrap a Python 2.6 SSL socket, so we can call shutdown() """ if HAVE_PY26_SSL and (self.sslobj is not None): self.sock = self.sslobj.unwrap() self.sslobj = None def _read(self, n): """ It seems that SSL Objects read() method may not supply as much as you're asking for, at least with extremely large messages. somewhere > 16K - found this in the test_channel.py test_large unittest. """ result = self.sslobj.read(n) while len(result) < n: s = self.sslobj.read(n - len(result)) if not s: raise IOError('Socket closed') result += s return result def _write(self, s): """ Write a string out to the SSL socket fully. """ while s: n = self.sslobj.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 = bytes() def _read(self, n): """ Read exactly n bytes from the socket """ while len(self._read_buffer) < n: s = self.sock.recv(65536) if not s: raise IOError('Socket closed') self._read_buffer += s result = self._read_buffer[:n] self._read_buffer = self._read_buffer[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) amqplib-1.0.2/amqplib/client_0_8/abstract_channel.py0000644000175100017510000000661011600522673023204 0ustar barrypbarryp00000000000000""" Code common to Connection and Channel objects. """ # Copyright (C) 2007-2008 Barry Pederson # # 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 serialization import AMQPWriter try: bytes except NameError: # Python 2.5 and lower bytes = str __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): """ Support for Python >= 2.5 'with' statements. """ return self def __exit__(self, type, value, traceback): """ Support for Python >= 2.5 'with' statements. """ self.close() def _send_method(self, method_sig, args=bytes(), content=None): """ Send a method for our channel. """ if isinstance(args, AMQPWriter): args = args.getvalue() self.connection.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 amqp_method = self._METHOD_MAP.get(method_sig, None) if amqp_method is None: raise Exception('Unknown AMQP method (%d, %d)' % 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 = {} amqplib-1.0.2/amqplib/client_0_8/connection.py0000644000175100017510000006453011641125500022046 0ustar barrypbarryp00000000000000""" AMQP 0-8 Connections """ # Copyright (C) 2007-2008 Barry Pederson # # 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 abstract_channel import AbstractChannel from channel import Channel from exceptions import * from method_framing import MethodReader, MethodWriter from serialization import AMQPReader, AMQPWriter from transport import create_transport __all__ = [ 'Connection', ] # # Client property info that gets sent to the server on connection startup # LIBRARY_PROPERTIES = { 'library': 'Python amqplib', 'library_version': '1.0.2', } AMQP_LOGGER = logging.getLogger('amqplib') class Connection(AbstractChannel): """ The connection class provides methods for a client to establish a network connection to a server, and for both peers to operate the connection thereafter. GRAMMAR: connection = open-connection *use-connection close-connection open-connection = C:protocol-header S:START C:START-OK *challenge S:TUNE C:TUNE-OK C:OPEN S:OPEN-OK | S:REDIRECT challenge = S:SECURE C:SECURE-OK use-connection = *channel close-connection = C:CLOSE S:CLOSE-OK / S:CLOSE C:CLOSE-OK """ def __init__(self, host='localhost', userid='guest', password='guest', login_method='AMQPLAIN', login_response=None, virtual_host='/', locale='en_US', client_properties=None, ssl=False, insist=False, connect_timeout=None, **kwargs): """ Create a connection to the specified host, which should be a 'host[:port]', such as 'localhost', or '1.2.3.4:5672' (defaults to 'localhost', if a port is not specified then 5672 is used) If login_response is not specified, one is built up for you from userid and password if they are present. The 'ssl' parameter may be simply True/False, or for Python >= 2.6 a dictionary of options to pass to ssl.wrap_socket() such as requiring certain certificates. """ if (login_response is None) \ and (userid is not None) \ and (password is not None): login_response = AMQPWriter() login_response.write_table({'LOGIN': userid, 'PASSWORD': password}) login_response = login_response.getvalue()[4:] #Skip the length #at the beginning d = {} d.update(LIBRARY_PROPERTIES) if client_properties: d.update(client_properties) self.known_hosts = '' while True: self.channels = {} # The connection object itself is treated as channel 0 super(Connection, self).__init__(self, 0) self.transport = None # Properties set in the Tune method self.channel_max = 65535 self.frame_max = 131072 self.heartbeat = 0 # Properties set in the Start method self.version_major = 0 self.version_minor = 0 self.server_properties = {} self.mechanisms = [] self.locales = [] # Let the transport.py module setup the actual # socket connection to the broker. # self.transport = create_transport(host, connect_timeout, ssl) self.method_reader = MethodReader(self.transport) self.method_writer = MethodWriter(self.transport, self.frame_max) self.wait(allowed_methods=[ (10, 10), # start ]) self._x_start_ok(d, login_method, login_response, locale) self._wait_tune_ok = True while self._wait_tune_ok: self.wait(allowed_methods=[ (10, 20), # secure (10, 30), # tune ]) host = self._x_open(virtual_host, insist=insist) if host is None: # we weren't redirected return # we were redirected, close the socket, loop and try again try: self.close() except Exception: pass def _do_close(self): self.transport.close() self.transport = None temp_list = [x for x in self.channels.values() if x is not self] for ch in temp_list: ch._do_close() self.connection = self.channels = None def _get_free_channel_id(self): for i in xrange(1, self.channel_max+1): if i not in self.channels: return i raise AMQPException('No free channel ids, current=%d, channel_max=%d' % (len(self.channels), self.channel_max)) def _wait_method(self, channel_id, allowed_methods): """ Wait for a method from the server destined for a particular channel. """ # # Check the channel's deferred methods # method_queue = self.channels[channel_id].method_queue for queued_method in method_queue: method_sig = queued_method[0] if (allowed_methods is None) \ or (method_sig in allowed_methods) \ or (method_sig == (20, 40)): method_queue.remove(queued_method) return queued_method # # Nothing queued, need to wait for a method from the peer # while True: channel, method_sig, args, content = \ self.method_reader.read_method() if (channel == channel_id) \ and ((allowed_methods is None) \ or (method_sig in allowed_methods) \ or (method_sig == (20, 40))): return method_sig, args, content # # Certain methods like basic_return should be dispatched # immediately rather than being queued, even if they're not # one of the 'allowed_methods' we're looking for. # if (channel != 0) and (method_sig in Channel._IMMEDIATE_METHODS): self.channels[channel].dispatch_method(method_sig, args, content) continue # # Not the channel and/or method we were looking for. Queue # this method for later # self.channels[channel].method_queue.append((method_sig, args, content)) # # If we just queued up a method for channel 0 (the Connection # itself) it's probably a close method in reaction to some # error, so deal with it right away. # if channel == 0: self.wait() def channel(self, channel_id=None): """ Fetch a Channel object identified by the numeric channel_id, or create that object if it doesn't already exist. """ if channel_id in self.channels: return self.channels[channel_id] return Channel(self, channel_id) ################# def close(self, reply_code=0, reply_text='', method_sig=(0, 0)): """ request a connection close This method indicates that the sender wants to close the connection. This may be due to internal conditions (e.g. a forced shut-down) or due to an error handling a specific method, i.e. an exception. When a close is due to an exception, the sender provides the class and method id of the method which caused the exception. RULE: After sending this method any received method except the Close-OK method MUST be discarded. RULE: The peer sending this method MAY use a counter or timeout to detect failure of the other peer to respond correctly with the Close-OK method. RULE: When a server receives the Close method from a client it MUST delete all server-side resources associated with the client's context. A client CANNOT reconnect to a context after sending or receiving a Close method. PARAMETERS: reply_code: short The reply code. The AMQ reply codes are defined in AMQ RFC 011. reply_text: shortstr The localised reply text. This text can be logged as an aid to resolving issues. class_id: short failing method class When the close is provoked by a method exception, this is the class of the method. method_id: short failing method ID When the close is provoked by a method exception, this is the ID of the method. """ if self.transport is None: # already closed return args = AMQPWriter() args.write_short(reply_code) args.write_shortstr(reply_text) args.write_short(method_sig[0]) # class_id args.write_short(method_sig[1]) # method_id self._send_method((10, 60), args) return self.wait(allowed_methods=[ (10, 61), # Connection.close_ok ]) def _close(self, args): """ request a connection close This method indicates that the sender wants to close the connection. This may be due to internal conditions (e.g. a forced shut-down) or due to an error handling a specific method, i.e. an exception. When a close is due to an exception, the sender provides the class and method id of the method which caused the exception. RULE: After sending this method any received method except the Close-OK method MUST be discarded. RULE: The peer sending this method MAY use a counter or timeout to detect failure of the other peer to respond correctly with the Close-OK method. RULE: When a server receives the Close method from a client it MUST delete all server-side resources associated with the client's context. A client CANNOT reconnect to a context after sending or receiving a Close method. PARAMETERS: reply_code: short The reply code. The AMQ reply codes are defined in AMQ RFC 011. reply_text: shortstr The localised reply text. This text can be logged as an aid to resolving issues. class_id: short failing method class When the close is provoked by a method exception, this is the class of the method. method_id: short failing method ID When the close is provoked by a method exception, this is the ID of the method. """ reply_code = args.read_short() reply_text = args.read_shortstr() class_id = args.read_short() method_id = args.read_short() self._x_close_ok() raise AMQPConnectionException(reply_code, reply_text, (class_id, method_id)) def _x_close_ok(self): """ confirm a connection close This method confirms a Connection.Close method and tells the recipient that it is safe to release resources for the connection and close the socket. RULE: A peer that detects a socket closure without having received a Close-Ok handshake method SHOULD log the error. """ self._send_method((10, 61)) self._do_close() def _close_ok(self, args): """ confirm a connection close This method confirms a Connection.Close method and tells the recipient that it is safe to release resources for the connection and close the socket. RULE: A peer that detects a socket closure without having received a Close-Ok handshake method SHOULD log the error. """ self._do_close() def _x_open(self, virtual_host, capabilities='', insist=False): """ open connection to virtual host This method opens a connection to a virtual host, which is a collection of resources, and acts to separate multiple application domains within a server. RULE: The client MUST open the context before doing any work on the connection. PARAMETERS: virtual_host: shortstr virtual host name The name of the virtual host to work with. RULE: If the server supports multiple virtual hosts, it MUST enforce a full separation of exchanges, queues, and all associated entities per virtual host. An application, connected to a specific virtual host, MUST NOT be able to access resources of another virtual host. RULE: The server SHOULD verify that the client has permission to access the specified virtual host. RULE: The server MAY configure arbitrary limits per virtual host, such as the number of each type of entity that may be used, per connection and/or in total. capabilities: shortstr required capabilities The client may specify a number of capability names, delimited by spaces. The server can use this string to how to process the client's connection request. insist: boolean insist on connecting to server In a configuration with multiple load-sharing servers, the server may respond to a Connection.Open method with a Connection.Redirect. The insist option tells the server that the client is insisting on a connection to the specified server. RULE: When the client uses the insist option, the server SHOULD accept the client connection unless it is technically unable to do so. """ args = AMQPWriter() args.write_shortstr(virtual_host) args.write_shortstr(capabilities) args.write_bit(insist) self._send_method((10, 40), args) return self.wait(allowed_methods=[ (10, 41), # Connection.open_ok (10, 50), # Connection.redirect ]) def _open_ok(self, args): """ signal that the connection is ready This method signals to the client that the connection is ready for use. PARAMETERS: known_hosts: shortstr """ self.known_hosts = args.read_shortstr() AMQP_LOGGER.debug('Open OK! known_hosts [%s]' % self.known_hosts) return None def _redirect(self, args): """ asks the client to use a different server This method redirects the client to another server, based on the requested virtual host and/or capabilities. RULE: When getting the Connection.Redirect method, the client SHOULD reconnect to the host specified, and if that host is not present, to any of the hosts specified in the known-hosts list. PARAMETERS: host: shortstr server to connect to Specifies the server to connect to. This is an IP address or a DNS name, optionally followed by a colon and a port number. If no port number is specified, the client should use the default port number for the protocol. known_hosts: shortstr """ host = args.read_shortstr() self.known_hosts = args.read_shortstr() AMQP_LOGGER.debug('Redirected to [%s], known_hosts [%s]' % (host, self.known_hosts)) return host def _secure(self, args): """ security mechanism challenge The SASL protocol works by exchanging challenges and responses until both peers have received sufficient information to authenticate each other. This method challenges the client to provide more information. PARAMETERS: challenge: longstr security challenge data Challenge information, a block of opaque binary data passed to the security mechanism. """ challenge = args.read_longstr() def _x_secure_ok(self, response): """ security mechanism response This method attempts to authenticate, passing a block of SASL data for the security mechanism at the server side. PARAMETERS: response: longstr security response data A block of opaque data passed to the security mechanism. The contents of this data are defined by the SASL security mechanism. """ args = AMQPWriter() args.write_longstr(response) self._send_method((10, 21), args) def _start(self, args): """ start connection negotiation This method starts the connection negotiation process by telling the client the protocol version that the server proposes, along with a list of security mechanisms which the client can use for authentication. RULE: If the client cannot handle the protocol version suggested by the server it MUST close the socket connection. RULE: The server MUST provide a protocol version that is lower than or equal to that requested by the client in the protocol header. If the server cannot support the specified protocol it MUST NOT send this method, but MUST close the socket connection. PARAMETERS: version_major: octet protocol major version The protocol major version that the server agrees to use, which cannot be higher than the client's major version. version_minor: octet protocol major version The protocol minor version that the server agrees to use, which cannot be higher than the client's minor version. server_properties: table server properties mechanisms: longstr available security mechanisms A list of the security mechanisms that the server supports, delimited by spaces. Currently ASL supports these mechanisms: PLAIN. locales: longstr available message locales A list of the message locales that the server supports, delimited by spaces. The locale defines the language in which the server will send reply texts. RULE: All servers MUST support at least the en_US locale. """ self.version_major = args.read_octet() self.version_minor = args.read_octet() self.server_properties = args.read_table() self.mechanisms = args.read_longstr().split(' ') self.locales = args.read_longstr().split(' ') AMQP_LOGGER.debug('Start from server, version: %d.%d, properties: %s, mechanisms: %s, locales: %s' % (self.version_major, self.version_minor, str(self.server_properties), self.mechanisms, self.locales)) def _x_start_ok(self, client_properties, mechanism, response, locale): """ select security mechanism and locale This method selects a SASL security mechanism. ASL uses SASL (RFC2222) to negotiate authentication and encryption. PARAMETERS: client_properties: table client properties mechanism: shortstr selected security mechanism A single security mechanisms selected by the client, which must be one of those specified by the server. RULE: The client SHOULD authenticate using the highest- level security profile it can handle from the list provided by the server. RULE: The mechanism field MUST contain one of the security mechanisms proposed by the server in the Start method. If it doesn't, the server MUST close the socket. response: longstr security response data A block of opaque data passed to the security mechanism. The contents of this data are defined by the SASL security mechanism. For the PLAIN security mechanism this is defined as a field table holding two fields, LOGIN and PASSWORD. locale: shortstr selected message locale A single message local selected by the client, which must be one of those specified by the server. """ args = AMQPWriter() args.write_table(client_properties) args.write_shortstr(mechanism) args.write_longstr(response) args.write_shortstr(locale) self._send_method((10, 11), args) def _tune(self, args): """ propose connection tuning parameters This method proposes a set of connection configuration values to the client. The client can accept and/or adjust these. PARAMETERS: channel_max: short proposed maximum channels The maximum total number of channels that the server allows per connection. Zero means that the server does not impose a fixed limit, but the number of allowed channels may be limited by available server resources. frame_max: long proposed maximum frame size The largest frame size that the server proposes for the connection. The client can negotiate a lower value. Zero means that the server does not impose any specific limit but may reject very large frames if it cannot allocate resources for them. RULE: Until the frame-max has been negotiated, both peers MUST accept frames of up to 4096 octets large. The minimum non-zero value for the frame- max field is 4096. heartbeat: short desired heartbeat delay The delay, in seconds, of the connection heartbeat that the server wants. Zero means the server does not want a heartbeat. """ self.channel_max = args.read_short() or self.channel_max self.frame_max = args.read_long() or self.frame_max self.method_writer.frame_max = self.frame_max self.heartbeat = args.read_short() self._x_tune_ok(self.channel_max, self.frame_max, 0) def _x_tune_ok(self, channel_max, frame_max, heartbeat): """ negotiate connection tuning parameters This method sends the client's connection tuning parameters to the server. Certain fields are negotiated, others provide capability information. PARAMETERS: channel_max: short negotiated maximum channels The maximum total number of channels that the client will use per connection. May not be higher than the value specified by the server. RULE: The server MAY ignore the channel-max value or MAY use it for tuning its resource allocation. frame_max: long negotiated maximum frame size The largest frame size that the client and server will use for the connection. Zero means that the client does not impose any specific limit but may reject very large frames if it cannot allocate resources for them. Note that the frame-max limit applies principally to content frames, where large contents can be broken into frames of arbitrary size. RULE: Until the frame-max has been negotiated, both peers must accept frames of up to 4096 octets large. The minimum non-zero value for the frame- max field is 4096. heartbeat: short desired heartbeat delay The delay, in seconds, of the connection heartbeat that the client wants. Zero means the client does not want a heartbeat. """ args = AMQPWriter() args.write_short(channel_max) args.write_long(frame_max) args.write_short(heartbeat) self._send_method((10, 31), args) self._wait_tune_ok = False _METHOD_MAP = { (10, 10): _start, (10, 20): _secure, (10, 30): _tune, (10, 41): _open_ok, (10, 50): _redirect, (10, 60): _close, (10, 61): _close_ok, } _IMMEDIATE_METHODS = [] amqplib-1.0.2/amqplib/client_0_8/method_framing.py0000644000175100017510000001725711544411075022705 0ustar barrypbarryp00000000000000""" Convert between frames and higher-level AMQP methods """ # Copyright (C) 2007-2008 Barry Pederson # # 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 Queue import Empty, Queue from struct import pack, unpack try: bytes except NameError: # Python 2.5 and lower bytes = str try: from collections import defaultdict except: class defaultdict(dict): """ Mini-implementation of collections.defaultdict that appears in Python 2.5 and up. """ def __init__(self, default_factory): dict.__init__(self) self.default_factory = default_factory def __getitem__(self, key): try: return dict.__getitem__(self, key) except KeyError: result = self.default_factory() dict.__setitem__(self, key, result) return result from basic_message import Message from exceptions import * 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): 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): self.body_parts.append(payload) self.body_received += len(payload) if self.body_received == self.body_size: self.msg.body = bytes().join(self.body_parts) self.complete = True 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 AMQPConnectionException is placed in the queue. In the case of unexpected frames, a tuple made up of (channel, AMQPChannelException) is placed in the queue. """ def __init__(self, source): self.source = source self.queue = Queue() self.running = False self.partial_messages = {} # For each channel, which type is expected next self.expected_types = defaultdict(lambda:1) 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. """ while self.queue.empty(): try: frame_type, channel, payload = self.source.read_frame() except Exception, e: # # Connection was closed? Framing Error? # self.queue.put(e) break if self.expected_types[channel] != frame_type: self.queue.put(( channel, Exception('Received frame type %s while expecting type: %s' % (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) 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) self.expected_types[channel] = 2 else: self.queue.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.queue.put((channel, partial.method_sig, partial.args, partial.msg)) del self.partial_messages[channel] 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.queue.put((channel, partial.method_sig, partial.args, partial.msg)) del self.partial_messages[channel] self.expected_types[channel] = 1 def read_method(self): """ Read a method from the peer. """ self._next_method() m = self.queue.get() if isinstance(m, Exception): raise m 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 def write_method(self, channel, method_sig, args, content=None): 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, unicode): 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() self.dest.write_frame(1, channel, payload) if content: payload = pack('>HHQ', method_sig[0], 0, len(body)) + properties self.dest.write_frame(2, channel, payload) chunk_size = self.frame_max - 8 for i in xrange(0, len(body), chunk_size): self.dest.write_frame(3, channel, body[i:i+chunk_size]) amqplib-1.0.2/amqplib/client_0_8/__init__.py0000644000175100017510000000232511544150127021445 0ustar barrypbarryp00000000000000""" AMQP Client implementing the 0-8 spec. """ # Copyright (C) 2007-2008 Barry Pederson # # 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 # # Pull in the public items from the various sub-modules # from basic_message import * from channel import * from connection import * from exceptions import * __all__ = [ 'Connection', 'Channel', # here mainly so it shows in in pydoc 'Message', 'AMQPException', 'AMQPConnectionException', 'AMQPChannelException', ] amqplib-1.0.2/amqplib/client_0_8/serialization.py0000644000175100017510000003376111610731766022603 0ustar barrypbarryp00000000000000""" Convert between bytestreams and higher-level AMQP types. 2007-11-05 Barry Pederson """ # Copyright (C) 2007 Barry Pederson # # 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 string import sys from datetime import datetime from decimal import Decimal from struct import pack, unpack from time import mktime IS_PY3K = sys.version_info[0] >= 3 if IS_PY3K: def byte(n): return bytes([n]) else: byte = chr try: from io import BytesIO except: # Python 2.5 and lower try: from cStringIO import StringIO as BytesIO except: from StringIO import StringIO as BytesIO try: bytes except NameError: # Python 2.5 and lower bytes = str 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_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() ftype = ord(table_data.input.read(1)) if ftype == 83: # 'S' val = table_data.read_longstr() elif ftype == 73: # 'I' val = unpack('>i', table_data.input.read(4))[0] elif ftype == 68: # 'D' d = table_data.read_octet() n = unpack('>i', table_data.input.read(4))[0] val = Decimal(n) / Decimal(10 ** d) elif ftype == 84: # 'T' val = table_data.read_timestamp() elif ftype == 70: # 'F' val = table_data.read_table() # recurse else: raise ValueError('Unknown table item type: %s' % repr(ftype)) result[name] = 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. """ if dest is None: self.out = BytesIO() else: self.out = dest self.bits = [] self.bitcount = 0 def _flushbits(self): if self.bits: for b in self.bits: self.out.write(pack('B', b)) self.bits = [] self.bitcount = 0 def close(self): """ Pass through if possible to any file-like destinations. """ if hasattr(self.out, 'close'): self.out.close() def flush(self): """ Pass through if possible to any file-like destinations. """ if hasattr(self.out, 'flush'): self.out.flush() 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. """ if b: b = 1 else: b = 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 ValueError('Octet out of range 0..255') 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 ValueError('Octet out of range 0..65535') self._flushbits() self.out.write(pack('>H', n)) def write_long(self, n): """ Write an integer as an unsigned2 32-bit value. """ if (n < 0) or (n >= (2**32)): raise ValueError('Octet out of range 0..2**31-1') 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 >= (2**64)): raise ValueError('Octet out of range 0..2**64-1') 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, unicode): s = s.encode('utf-8') if len(s) > 255: raise ValueError('String too long') 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, unicode): 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 d.items(): table_data.write_shortstr(k) if isinstance(v, basestring): if isinstance(v, unicode): v = v.encode('utf-8') table_data.write(byte(83)) # 'S' table_data.write_longstr(v) elif isinstance(v, (int, long)): table_data.write(byte(73)) # 'I' table_data.write(pack('>i', v)) elif isinstance(v, Decimal): table_data.write(byte(68)) # 'D' sign, digits, exponent = v.as_tuple() v = 0 for d in digits: v = (v * 10) + d if sign: v = -v table_data.write_octet(-exponent) table_data.write(pack('>i', v)) elif isinstance(v, datetime): table_data.write(byte(84)) # 'T' table_data.write_timestamp(v) ## FIXME: timezone ? elif isinstance(v, dict): table_data.write(byte(70)) # 'F' table_data.write_table(v) else: raise ValueError('%s not serializable in AMQP' % repr(v)) table_data = table_data.getvalue() self.write_long(len(table_data)) self.out.write(table_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(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. """ return hasattr(other, 'properties') \ and (self.properties == other.properties) 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 __ne__(self, other): """ Just return the opposite of __eq__ """ return not self.__eq__(other) 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 True: 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() amqplib-1.0.2/amqplib/__init__.py0000644000175100017510000000000110735225412017507 0ustar barrypbarryp00000000000000 amqplib-1.0.2/INSTALL0000644000175100017510000000010710735225412015011 0ustar barrypbarryp00000000000000INSTALLATION: From the command-line run: python setup.py install amqplib-1.0.2/README0000644000175100017510000000033711544154340014645 0ustar barrypbarryp00000000000000Python AMQP (Advanced Message Queuing Protocol) Client library For more information about AMQP, visit http://www.amqp.org For other Python client libraries see: http://www.rabbitmq.com/devtools.html#python-dev amqplib-1.0.2/PKG-INFO0000644000175100017510000000153011641133256015057 0ustar barrypbarryp00000000000000Metadata-Version: 1.0 Name: amqplib Version: 1.0.2 Summary: AMQP Client Library Home-page: http://code.google.com/p/py-amqplib/ Author: Barry Pederson Author-email: bp@barryp.org License: LGPL Description: UNKNOWN Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 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: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Classifier: Intended Audience :: Developers amqplib-1.0.2/setup.py0000644000175100017510000000254111641125460015475 0ustar barrypbarryp00000000000000#!/usr/bin/env python """ Python Distutils setup for for amqp. Build and install with python setup.py install 2007-11-10 Barry Pederson """ import sys try: from setuptools import setup except: from distutils.core import setup try: from distutils.command.build_py import build_py_2to3 as build_py except ImportError: # 2.x from distutils.command.build_py import build_py setup(name = "amqplib", description = "AMQP Client Library", version = "1.0.2", classifiers=[ 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.4', 'Programming Language :: Python :: 2.5', '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', 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', 'Intended Audience :: Developers', ], license = "LGPL", author = "Barry Pederson", author_email = "bp@barryp.org", url = "http://code.google.com/p/py-amqplib/", packages = ['amqplib', 'amqplib.client_0_8'], cmdclass = {'build_py':build_py}, ) amqplib-1.0.2/CHANGES0000644000175100017510000001450511641125560014762 0ustar barrypbarryp00000000000000CHANGES -------- Version 1.0.2 Fix a packaging problem for Windows users, no changes to the actual Python code. Version 1.0.1 A one-line change to transport.py to fix a problem older FreeBSDs have with socket.getaddrinfo() Otherwise just packaging changes to include more Trove classifiers and docs/tests/etc in the source distribution file. Version 1.0.0 Big speedup for sending large messages. For example, sending a single 100MB message on my machine goes from 90 seconds to around 0.6 seconds. Use setuptools if available, for enhanced functionality for packagers. Raise a ValueError if unserializable objects are present in a Message's application_headers, instead of just quietly failing and causing a connection to close. Message objects can now be pickled/unpickled, previously unpickling raised a RuntimeError: maximum recursion depth exceeded PYTHON 3.x COMPATIBILITY Code has been tweaked so that when 2to3 is run over the client library and the unittests, the unittests will pass for Python 3.0, 3.1 and 3.2 There are some subtle behavioral changes to deal with how Python 3.x needs to encode/decode strings and they go/come over the wire. Message bodies are encoded at transmission time, instead of Message object creation time. Message application_header strings are assumed to be encoded as UTF-8 Add support for queue_unbind, since RabbitMQ supports it as an extension to the 0-8 protocol. Add IPv6 support. The client uses socket.getaddrinfo(), so you can use domain names with AAAA DNS records, or IPv6 literal addresses. If you need to specify a port number along with a literal address, put the address in square brackets (see RFC 2732), for example: [::1]:5672 Some minor TCP changes, enabling keepalive, NODELAY (big speed improvement there), shutting down sockets before closing to keep from losing data (on Windows mainly?) Version 0.6.1 One minor change to watch out for is that low-level errors such as a closed connection now appear as IOExceptions instead of TypeError: 'NoneType' object is not iterable which never really explained anything. Fix potential problem with library getting stuck in a loop if the peer closed the socket. Also, break a few more references when closing Connections and Channels, to help garbage collection. Thanks for majek04@... for pointing these out. Add support for using Connection and Channel objects as context managers for 'with' statements (available in Python >= 2.5), so you can write code like: with Connection() as conn: with conn.channel() as ch: # do stuff with ch and conn and have the Channel and Connection objects closed automatically when the blocks exit. Version 0.6 Very large rearrangement of code, breaking the large client_0_8.py module into submodules based on the various layers of the AMQP protocol. The public API is unchanged however, so existing code that uses amqplib should be unaffected. ---- nb_client_0_8.py and demos/nbdemo_receive.py were removed because of the major changes to the main client to lay the groundwork for future non-blocking behaviors (see http://hg.barryp.org/py-amqplib-devel/ ) ---- More unittests added. Version 0.5 Get rid of Python 2.5-style conditional expressions, for compatibility with Python 2.4 Thanks to Alexey Timanovsky for pointing this out. Send debugging output through the standard Python 'logging' module instead of directly printing to the console. Reworked the guts of the Connection and Channel classes to untangle the mess that controlled how frames were waited for and queued up. Should fix problems seen with basic_deliver messages coming when the client is expecting responses to other synchronous calls. Added non-blocking client and demo, from Dmitriy Samovskiy : ----------------------- We put together an add-on for py-amqplib that implements AMQP client with non-blocking sockets (see NonBlockingConnection class and nbloop() function in nbclient_0_8.py). nbdemo_receive.py is a demo script, and nbclient.zip includes both nbclient_0_8.py and nbdemo_receive.py. There are at least 2 scenarios where non-blocking sockets help, and both are applicable to consumers: 1) when you want to be able to interrupt consumer's event loop without waiting for a next incoming message; 2) when you want to consume messages from multiple brokers (or over multiple connections) in a single event loop. Did some profiling, found a big problem that caused a huge huge number of unnecessary __getattr_ calls. Ran pylint and found some bad coding style problems. Version 0.3 Improved skeleton generating program to include much more information in the Python docstrings that was present in the AMQP spec file. Merged in the improved docstrings into the client module. After having a better look now at the pydocs, it turns out that in several methods, a default value of '' can be used for queue names and exchange names, so update method signatures to take advantage of that. Channel.queue_bind() can also take '' as a queue parameter, but unfortunately we can't set that as a default value because the exchange parameter can't have a default value. In hindsight those args should have been swapped, but it's too late now. Deal with no callback being specified in basic_consume(), it should now quietly discard messages. Version 0.2 Changed the default value for the auto_delete parameter in the Channel.exchange_declare and Channel.queue_declare methods to True. Added an 'insist' parameter to the Connection class constructor, defaulting to False. Setting it to True indicates to the AMQP server that you don't want to be redirected (you're insisting to connect to the specified server). Added support for being redirected to another AMQP server when a Connection is opened. Added tests/fake_redirect_0_8.py to help with testing redirect support. Version 0.1 Initial version amqplib-1.0.2/docs/0000755000175100017510000000000011641133256014713 5ustar barrypbarryp00000000000000amqplib-1.0.2/docs/overview.txt0000644000175100017510000001704211600262355017324 0ustar barrypbarryp00000000000000Notes on using Python amqplib For more detailed descriptions of AMQP concepts, visit https://jira.amqp.org/confluence/display/AMQP/Download This doc just covers some example usages of the Python library. +---------------------------------------------------------------- | | IMPORTANT!! - Connections are NOT thread-safe. A multithreaded | Python program can only use amqplib as long as | each thread has its own Connection object, and | does not try to share amqplib objects such as | Connections or Channels with other threads. | +---------------------------------------------------------------- Importing the client -------------------- This library currently supports the AMQP 0-8 spec, importing a module geared towards that spec can be done with something like: import amqplib.client_0_8 as amqp Connections ---------- A connection object represents a network connection to the server. Some examples for creating a Connection object: conn = amqp.Connection() conn = amqp.Connection('1.2.3.4') conn = amqp.Connection('::1') # IPv6 conn = amqp.Connection('[1234::1]:5671') # IPv6 conn = amqp.Connection('foobar.edu:5671') # could be IPv4 or IPv6 conn = amqp.Connection(host='1.2.3.4:5671', ssl=True) conn = amqp.Connection(userid='foo', password='bar') Fancier SSL options are available with Python >= 2.6 by specifying a dictionary of options as the "ssl" parameter (see the standard Python library ssl.wrap_socket() function documentation) import ssl conn = amqp.Connection(host='1.2.3.4:5671', ssl={ 'ca_certs': '/path/to/cacert.pem', 'keyfile' : '/path/to/key.pem', 'certfile': '/path/to/cert.pem', 'cert_reqs': ssl.CERT_REQUIRED, }) Connections are closed with the close() method conn.close() Channels ---------- Once a Connection is open, one or more Channel objects may be opened, acting as virtual connections between peers. Channels are where most of the action is. ch = conn.channel(1) # create and open channel with a specific numeric id ch2 = conn.channel() # let amqplib assign a channel number print 'my channel is', ch2.channel_id ch3 = conn.channel(1) # since channel 1 is already created and open, just # return another reference to that. Channels have a close() method too, which doesn't affect other channels ch2.close() +---------------------------------------------------------------- | | IMPORTANT!! - be sure to close your channels or connection | if you've been calling async methods like | basic_publish() - to ensure your messages are | actually flushed out the TCP socket before the | program ends, instead of potentially being lost. | +---------------------------------------------------------------- Connections and Channels will close automatically when used as context managers in 'with' statements (available in Python 2.5 or higher). For example: with Connection('1.2.3.4') as conn: with conn.channel() as ch: # # Do some stuff... # ch.basic_publish(blah, blah, blah) # When this point is reached, the Channel will have been # closed for you. # When this point is reached, the Connection will have been # closed for you. Access Tickets -------------- Before a channel can do much else, it needs an access ticket, specifying a 'realm', and the actions it wants to perform on that realm. tkt = ch.access_request('/data', active=True, write=True, read=True) ch.exchange_declare('myfan', 'fanout', auto_delete=True, ticket=tkt) The most recently requested ticket is saved in the Channel object, and used as a default value for other methods that require tickets. So it's not necessary to manually keep and pass it around. A simpler version of the above code is: ch.access_request('/data', active=True, write=True, read=True) ch.exchange_declare('myfan', 'fanout', auto_delete=True) Messages --------- This library only supports the Basic Content type, although the class was named 'Message' based on what's coming in the 0-10 spec. A Message can be created as simply as msg = amqp.Message('hello world') Unicode bodies are converted to UTF-8 and the 'content_encoding' property is automatically set to 'UTF-8', so these two Messages are equivalent msg1 = amqp.Message(u'Unicode hello') msg2 = amqp.Message(u'Unicode hello'.encode('utf-8'), content_encoding='UTF-8') Messages may have an 'application_headers' dictionary (another 0-10ism, 0-8 just calls the property 'headers'), consisting of string keys and values that are strings, integers, Decimal (a bit shaky), datetime.datetime, and other dictionaries containing those same types. msg = amqp.Message('fancy', application_headers={'foo': 7, 'bar': 'baz'}) Exchanges & Queues ------------------- In the AMQP model, publishers send messages to exchanges, which then distribute them to queues, which deliver them to receivers. AMQP defines some default exchanges such as, 'amq.direct', 'amq.topic', 'amq.fanout' - which can be used right away. To send to one of these default exchanges: ch.basic_publish(msg, 'amq.fanout') To declare your own exchange and send it a message: ch.exchange_declare('myfan', type='fanout') ch.basic_publish(msg, 'myfan') To receive messages, a program must declare a queue, and bind it to an exchange: ch.queue_declare('myqueue') ch.queue_bind('myqueue', 'amq.fanout') If the queue name is omitted from the queue_declare() method, a unique one is generated by the server and returned (along with message_count and consumer_count values which we don't care about in this case). qname, _, _ = ch.queue_declare() ch.queue_bind(qname, 'amq.fanout') Receiving Messages ------------------ Once a queue is bound to an exchange, messages can be received either by polling or by waiting on the channel. Polling with basic_get() immediately returns either a Message object, or None if no messages are available: msg = ch.basic_get('myqueue') if msg is not None: # do something ch.basic_ack(msg.delivery_tag) Received messages are acknowledged with the Channel.basic_ack() method unless we called basic_get() with a no_ack=True parameter. To block and wait for messages, use basic_consume() with a callback function, and then wait() on the channel. The AMQP server will deliver messages to the channel as they're placed into the queue. def mycallback(msg): print 'received', msg.body, 'from channel #', msg.channel.channel_id ch.basic_consume('myqueue', callback=mycallback, no_ack=True) while True: ch.wait() The above example will loop forever. To cancel the 'consume', use basic_cancel() with a consumer_tag. A consumer_tag is a string that's either specified when you call basic_consume() ch.basic_consume('myqueue', callback=mycallback, consumer_tag='mytag') ... ch.basic_cancel('mytag') or uniquely generated by the server. tag = ch.basic_consume('myqueue', callback=mycallback) ... ch.basic_cancel(tag) The consumer_tag is also included as a property in any Message objects delivered through a callback. msg.channel.basic_cancel(msg.consumer_tag) A Channel object has a 'callbacks' property that can be checked to see if there are any outstanding basic_consume() operations registered. The demo/demo_send.py script uses this to allow for breaking out of a waiting loop cleanly. ### EOF ### amqplib-1.0.2/docs/code_layout.txt0000644000175100017510000000507011242022112017746 0ustar barrypbarryp00000000000000General layout of the Python code for those interested in hacking on it. ------------------------------------------------------------------------ The Connection class in connection.py is the heart of the library, it wires together the various other classes. The items in method_framing.py and transport.py should not be visible to the amqplib user. This is the general arrangement of how the classes communicate with each other: +-----------+ +-----------+ channel.py | Channel | ... | Channel | +-----------+ +-----------+ ^ ^ | methods | V V +--------------------------------------+ connection.py | Connection | +-------+------------------------------+ | ^ | methods | V | +----------------+ +--------+-------+ method_framing.py | MethodWriter | | MethodReader | +-------+--------+ +----------------+ | ^ | frames | V | +------------------------------+-------+ transport.py | TCPTransport or SSLTransport | +-------+------------------------------+ | ^ | bytes | V | +--------------------------+--+ | TCP Socket | +-----------------------------+ Other notable bits of code are: abstract_channel.py - Contains a common superclass for Connection and Channel classes. basic_message.py - Location of the Message class - which is what is mostly being sent/received over the Channels. exceptions.py - For indicating when things go wrong. serialization.py - Home of AMQPReader and AMQPWriter which are used in many places within the Channel and Connection classes. amqplib-1.0.2/MANIFEST.in0000644000175100017510000000024711641111045015514 0ustar barrypbarryp00000000000000include README TODO CHANGES INSTALL LICENSE recursive-include docs *.txt recursive-include demo *.py recursive-include extras README *.py recursive-include tests *.py amqplib-1.0.2/amqplib.egg-info/0000755000175100017510000000000011641133256017102 5ustar barrypbarryp00000000000000amqplib-1.0.2/amqplib.egg-info/top_level.txt0000644000175100017510000000001011641133256021623 0ustar barrypbarryp00000000000000amqplib amqplib-1.0.2/amqplib.egg-info/dependency_links.txt0000644000175100017510000000000111641133256023150 0ustar barrypbarryp00000000000000 amqplib-1.0.2/amqplib.egg-info/SOURCES.txt0000644000175100017510000000167211641133256020774 0ustar barrypbarryp00000000000000CHANGES INSTALL LICENSE MANIFEST.in README TODO setup.py amqplib/__init__.py amqplib.egg-info/PKG-INFO amqplib.egg-info/SOURCES.txt amqplib.egg-info/dependency_links.txt amqplib.egg-info/top_level.txt amqplib/client_0_8/__init__.py amqplib/client_0_8/abstract_channel.py amqplib/client_0_8/basic_message.py amqplib/client_0_8/channel.py amqplib/client_0_8/connection.py amqplib/client_0_8/exceptions.py amqplib/client_0_8/method_framing.py amqplib/client_0_8/serialization.py amqplib/client_0_8/transport.py demo/amqp_clock.py demo/demo_receive.py demo/demo_send.py docs/code_layout.txt docs/overview.txt extras/README extras/generate_skeleton_0_8.py tests/client_0_8/fake_redirect.py tests/client_0_8/run_all.py tests/client_0_8/settings.py tests/client_0_8/test_basic_message.py tests/client_0_8/test_channel.py tests/client_0_8/test_connection.py tests/client_0_8/test_exceptions.py tests/client_0_8/test_serialization.py tests/client_0_8/test_with.pyamqplib-1.0.2/amqplib.egg-info/PKG-INFO0000644000175100017510000000153011641133256020176 0ustar barrypbarryp00000000000000Metadata-Version: 1.0 Name: amqplib Version: 1.0.2 Summary: AMQP Client Library Home-page: http://code.google.com/p/py-amqplib/ Author: Barry Pederson Author-email: bp@barryp.org License: LGPL Description: UNKNOWN Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 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: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Classifier: Intended Audience :: Developers amqplib-1.0.2/tests/0000755000175100017510000000000011641133256015125 5ustar barrypbarryp00000000000000amqplib-1.0.2/tests/client_0_8/0000755000175100017510000000000011641133256017051 5ustar barrypbarryp00000000000000amqplib-1.0.2/tests/client_0_8/test_channel.py0000755000175100017510000002331111610742063022073 0ustar barrypbarryp00000000000000#!/usr/bin/env python """ Test amqplib.client_0_8.channel module """ # Copyright (C) 2007-2008 Barry Pederson # # 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 time import unittest try: bytes except NameError: # Python 2.5 and lower bytes = str import settings from amqplib.client_0_8 import AMQPChannelException, AMQPException, Connection, Message 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. """ self.ch.access_request('/data', active=True, write=True, read=True) msg = Message('unittest 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): self.ch.access_request('/data', active=True, write=True, read=True) my_routing_key = 'unittest.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 # test_bytes = u'hello w\xd6rld'.encode('latin_1') # don't really care about latin_1, just want bytes 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_exception(self): """ Check that Channel exceptions are actually raised as Python exceptions. """ self.assertRaises(AMQPChannelException, self.ch.queue_delete, 'bogus_queue_that_does_not_exist') 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 """ self.ch.access_request('/data', active=True, write=True, read=True) qname, _, _ = self.ch.queue_declare() msg = Message(application_headers={'test': None}) self.assertRaises(ValueError, self.ch.basic_publish, msg, routing_key=qname) def test_large(self): """ Test sending some extra large messages. """ self.ch.access_request('/data', active=True, write=True, read=True) qname, _, _ = self.ch.queue_declare() for multiplier in [100, 1000, 10000]: msg = Message('unittest 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): tkt = self.ch.access_request('/data', active=True, write=True) self.assertEqual(tkt, self.ch.default_ticket) self.ch.exchange_declare('unittest.fanout', 'fanout', auto_delete=True) msg = Message('unittest message', content_type='text/plain', application_headers={'foo': 7, 'bar': 'baz'}) self.ch.basic_publish(msg, 'unittest.fanout') def test_queue(self): self.ch.access_request('/data', active=True, write=True, read=True) my_routing_key = 'unittest.test_queue' msg = Message('unittest 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): self.ch.access_request('/data', active=True, write=True, read=True) my_routing_key = 'unittest.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.access_request('/data', active=True, write=True) self.ch.exchange_declare('unittest.fanout', 'fanout', auto_delete=True) msg = Message('unittest message', content_type='text/plain', application_headers={'foo': 7, 'bar': 'baz'}) self.ch.basic_publish(msg, 'unittest.fanout') self.ch.basic_publish(msg, 'unittest.fanout', immediate=True) self.ch.basic_publish(msg, 'unittest.fanout', mandatory=True) self.ch.basic_publish(msg, 'unittest.fanout', immediate=True, 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 main(): suite = unittest.TestLoader().loadTestsFromTestCase(TestChannel) unittest.TextTestRunner(**settings.test_args).run(suite) if __name__ == '__main__': main() amqplib-1.0.2/tests/client_0_8/test_with.py0000644000175100017510000000423611544150127021440 0ustar barrypbarryp00000000000000#!/usr/bin/env python """ Test support for 'with' statements in Python >= 2.5 """ # Copyright (C) 2009 Barry Pederson # # 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 with_statement import unittest import settings from amqplib.client_0_8 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.access_request('/data', active=True, write=True) 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() amqplib-1.0.2/tests/client_0_8/run_all.py0000755000175100017510000000244711150601545021065 0ustar barrypbarryp00000000000000#!/usr/bin/env python """ Run all the unittest modules for amqplib.client_0_8 """ # Copyright (C) 2007-2008 Barry Pederson # # 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 TEST_NAMES = [ 'test_exceptions', 'test_serialization', 'test_basic_message', 'test_connection', 'test_channel', ] if sys.version_info >= (2, 5): TEST_NAMES.append('test_with') def main(): suite = unittest.TestLoader().loadTestsFromNames(TEST_NAMES) unittest.TextTestRunner(**settings.test_args).run(suite) if __name__ == '__main__': main() amqplib-1.0.2/tests/client_0_8/settings.py0000644000175100017510000000543211135126760021267 0ustar barrypbarryp00000000000000""" 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 # # 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('-v', '--verbose', 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) amqplib_logger = logging.getLogger('amqplib') amqplib_logger.addHandler(console) amqplib_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() amqplib-1.0.2/tests/client_0_8/test_serialization.py0000755000175100017510000002301311610731204023332 0ustar barrypbarryp00000000000000#!/usr/bin/env python """ Test amqplib.client_0_8.serialization, checking conversions between byte streams and higher level objects. """ # Copyright (C) 2007-2008 Barry Pederson # # 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 try: bytes except NameError: # Python 2.5 and lower bytes = str import settings from amqplib.client_0_8.serialization import AMQPReader, AMQPWriter, GenericContent 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(ValueError, w.write_octet, -1) def test_octet_invalid2(self): w = AMQPWriter() self.assertRaises(ValueError, 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(ValueError, w.write_short, -1) def test_short_invalid2(self): w = AMQPWriter() self.assertRaises(ValueError, w.write_short, 65536) # # Longs # def test_long(self): for i in range(256): val = randint(0, (2**32) - 1) 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(ValueError, w.write_long, -1) def test_long_invalid2(self): w = AMQPWriter() self.assertRaises(ValueError, w.write_long, 2**32) # # 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(ValueError, w.write_longlong, -1) def test_longlong_invalid2(self): w = AMQPWriter() self.assertRaises(ValueError, 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(ValueError, w.write_shortstr, 'x' * 256) def test_long_shortstr_unicode(self): w = AMQPWriter() self.assertRaises(ValueError, 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': None} w = AMQPWriter() self.assertRaises(ValueError, 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), '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) # # 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() amqplib-1.0.2/tests/client_0_8/test_connection.py0000755000175100017510000000753111241577310022631 0ustar barrypbarryp00000000000000#!/usr/bin/env python """ Test amqplib.client_0_8.connection module """ # Copyright (C) 2007-2008 Barry Pederson # # 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 sys import time import unittest import settings from amqplib.client_0_8 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 # ch = 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 ch = None 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() amqplib-1.0.2/tests/client_0_8/fake_redirect.py0000755000175100017510000001227511544150127022222 0ustar barrypbarryp00000000000000#!/usr/bin/env python """ Fake AMQP Redirect - simulate an AMQP server that redirects connections to another server. A bit ugly, but it's just to test that the client library actually handles a redirect, without having to have an unbalanced cluster of real AMQP servers. 2007-12-08 Barry Pederson """ # Copyright (C) 2007-2008 Barry Pederson # # 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 socket import sys from optparse import OptionParser from Queue import Queue import amqplib.client_0_8 as amqp from amqplib.client_0_8.connection import AMQP_PROTOCOL_HEADER, _MethodReader from amqplib.client_0_8.serialization import AMQPReader, AMQPWriter class FakeRedirectConnection(amqp.Connection): def __init__(self, sock): self.channels = {} super(amqp.Connection, self).__init__(self, 0) self.out = AMQPWriter(sock.makefile('w')) self.input = AMQPReader(sock.makefile('r')) self.method_reader = _MethodReader(self.input) def do_redirect(self, dest): if self.input.read(8) != AMQP_PROTOCOL_HEADER: print "Didn't receive AMQP 0-8 header" return # major, minor seems backwards, but that's what RabbitMQ sends self.start(8, 0, {'product': 'fake_redirect_0_8.py'}, ['AMQPLAIN'], ['en_US']) self.wait(allowed_methods=[ (10, 11), # start_ok ]) self.tune(0, 0, 0) self.wait(allowed_methods=[ (10, 31), # tune_ok ]) self.wait(allowed_methods=[ (10, 40), # open ]) if self.insist: self.close(reply_text="Can't redirect, insist was set to True") else: self.redirect(dest, '') try: self.wait(allowed_methods=[ (10, 60), # close ]) except amqp.AMQPConnectionException: pass print 'Redirect finished' def fake_op(self, args): """ We're not really much interested in what the client sends for start_ok, tune_ok """ pass ############## def _open(self, args): virtual_host = args.read_shortstr() capabilities = args.read_shortstr() self.insist = args.read_bit() def redirect(self, host, known_hosts): args = AMQPWriter() args.write_shortstr(host) args.write_shortstr(known_hosts) self._send_channel_method_frame(0, (10, 50), args) def start(self, version_major, version_minor, server_properties, mechanisms, locales): args = AMQPWriter() args.write_octet(version_major) args.write_octet(version_minor) args.write_table(server_properties) args.write_longstr(' '.join(mechanisms)) args.write_longstr(' '.join(locales)) self._send_channel_method_frame(0, (10, 10), args) def tune(self, channel_max, frame_max, heartbeat): args = AMQPWriter() args.write_short(channel_max) args.write_long(frame_max) args.write_short(heartbeat) self._send_channel_method_frame(0, (10, 30), args) # # Monkeypatch the amqplib.client_0_8.Connection _METHOD_MAP dict to # work with our FakeRedirectConnection # amqp.Connection._METHOD_MAP[(10, 11)] = FakeRedirectConnection.fake_op amqp.Connection._METHOD_MAP[(10, 31)] = FakeRedirectConnection.fake_op amqp.Connection._METHOD_MAP[(10, 40)] = FakeRedirectConnection._open def main(): parser = OptionParser(usage='usage: %prog [options]\nexample: %prog --listen=127.0.0.1:5000 --redirect=127.0.0.1:5672') parser.add_option('--listen', dest='listen', help='ip:port to listen for an AMQP connection on', default=None) parser.add_option('--redirect', dest='redirect', help='ip:port to redirect AMQP connection to', default=None) options, args = parser.parse_args() if not options.listen or not options.redirect: parser.print_help() sys.exit(1) listen_ip, listen_port = options.listen.split(':', 1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((listen_ip, int(listen_port))) print 'listening for connection...' s.listen(1) while True: sock, addr = s.accept() print 'Accepted connection from', addr conn = FakeRedirectConnection(sock) conn.do_redirect(options.redirect) if __name__ == '__main__': main() amqplib-1.0.2/tests/client_0_8/test_basic_message.py0000755000175100017510000000715311610732763023264 0ustar barrypbarryp00000000000000#!/usr/bin/env python """ Test the amqplib.client_0_8.basic_message module. """ # Copyright (C) 2007-2008 Barry Pederson # # 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: import pickle import settings from amqplib.client_0_8.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() n = n.replace(microsecond=0) # AMQP only does timestamps to 1-second resolution 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() amqplib-1.0.2/tests/client_0_8/test_exceptions.py0000755000175100017510000000273611135126760022656 0ustar barrypbarryp00000000000000#!/usr/bin/env python """ Test amqplib.client_0_8.exceptions module """ # Copyright (C) 2007-2008 Barry Pederson # # 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 amqplib.client_0_8.exceptions import * class TestException(unittest.TestCase): def test_exception(self): exc = AMQPException(7, 'My Error', (10, 10)) self.assertEqual(exc.amqp_reply_code, 7) self.assertEqual(exc.amqp_reply_text, 'My Error') self.assertEqual(exc.amqp_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() amqplib-1.0.2/setup.cfg0000644000175100017510000000007311641133256015604 0ustar barrypbarryp00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 amqplib-1.0.2/extras/0000755000175100017510000000000011641133256015271 5ustar barrypbarryp00000000000000amqplib-1.0.2/extras/generate_skeleton_0_8.py0000755000175100017510000002675210735225412022025 0ustar barrypbarryp00000000000000#!/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 """ # Copyright (C) 2007 Barry Pederson # # 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 optparse import OptionParser 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): """ 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) for d in element.findall('doc') + element.findall('rule'): 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 elif d.tag == 'rule': result.append(indent + 'RULE:') result.append(indent) extra_indent = ' ' 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 []' % 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()) amqplib-1.0.2/extras/README0000644000175100017510000000047610735225412016157 0ustar barrypbarryp00000000000000generate_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