amqp-1.4.9/0000755000076500000000000000000012644064471013105 5ustar asksolwheel00000000000000amqp-1.4.9/amqp/0000755000076500000000000000000012644064471014043 5ustar asksolwheel00000000000000amqp-1.4.9/amqp/__init__.py0000644000076500000000000000411612644064354016156 0ustar asksolwheel00000000000000"""Low-level AMQP client for Python (fork of amqplib)""" # 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 __future__ import absolute_import VERSION = (1, 4, 9) __version__ = '.'.join(map(str, VERSION[0:3])) + ''.join(VERSION[3:]) __author__ = 'Barry Pederson' __maintainer__ = 'Ask Solem' __contact__ = 'pyamqp@celeryproject.org' __homepage__ = 'http://github.com/celery/py-amqp' __docformat__ = 'restructuredtext' # -eof meta- # # Pull in the public items from the various sub-modules # from .basic_message import Message # noqa from .channel import Channel # noqa from .connection import Connection # noqa from .exceptions import ( # noqa AMQPError, ConnectionError, RecoverableConnectionError, IrrecoverableConnectionError, ChannelError, RecoverableChannelError, IrrecoverableChannelError, ConsumerCancelled, ContentTooLarge, NoConsumers, ConnectionForced, InvalidPath, AccessRefused, NotFound, ResourceLocked, PreconditionFailed, FrameError, FrameSyntaxError, InvalidCommand, ChannelNotOpen, UnexpectedFrame, ResourceError, NotAllowed, AMQPNotImplementedError, InternalError, error_for_code, __all__ as _all_exceptions, ) from .utils import promise # noqa __all__ = [ 'Connection', 'Channel', 'Message', ] + _all_exceptions amqp-1.4.9/amqp/abstract_channel.py0000644000076500000000000000654512644026054017715 0ustar asksolwheel00000000000000"""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 __future__ import absolute_import from .exceptions import AMQPNotImplementedError, RecoverableConnectionError from .serialization import AMQPWriter __all__ = ['AbstractChannel'] class AbstractChannel(object): """Superclass for both the Connection, which is treated as channel 0, and other user-created Channel objects. The subclasses must have a _METHOD_MAP class property, mapping between AMQP method signatures and Python methods. """ def __init__(self, connection, channel_id): self.connection = connection self.channel_id = channel_id connection.channels[channel_id] = self self.method_queue = [] # Higher level queue for methods self.auto_decode = False def __enter__(self): return self def __exit__(self, *exc_info): self.close() def _send_method(self, method_sig, args=bytes(), content=None): """Send a method for our channel.""" conn = self.connection if conn is None: raise RecoverableConnectionError('connection already closed') if isinstance(args, AMQPWriter): args = args.getvalue() conn.method_writer.write_method( self.channel_id, method_sig, args, content, ) def close(self): """Close this Channel or Connection""" raise NotImplementedError('Must be overriden in subclass') def wait(self, allowed_methods=None, timeout=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, timeout) return self.dispatch_method(method_sig, args, content) def dispatch_method(self, method_sig, args, content): if content and \ self.auto_decode and \ hasattr(content, 'content_encoding'): try: content.body = content.body.decode(content.content_encoding) except Exception: pass try: amqp_method = self._METHOD_MAP[method_sig] except KeyError: raise AMQPNotImplementedError( 'Unknown AMQP method {0!r}'.format(method_sig)) if content is None: return amqp_method(self, args) else: return amqp_method(self, args, content) #: Placeholder, the concrete implementations will have to #: supply their own versions of _METHOD_MAP _METHOD_MAP = {} amqp-1.4.9/amqp/basic_message.py0000644000076500000000000000756212644026054017207 0ustar asksolwheel00000000000000"""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 __future__ import absolute_import from .serialization import GenericContent __all__ = ['Message'] class Message(GenericContent): """A Message for use with the Channnel.basic_* methods.""" #: Instances of this class have these attributes, which #: are passed back and forth as message properties between #: client and server PROPERTIES = [ ('content_type', 'shortstr'), ('content_encoding', 'shortstr'), ('application_headers', 'table'), ('delivery_mode', 'octet'), ('priority', 'octet'), ('correlation_id', 'shortstr'), ('reply_to', 'shortstr'), ('expiration', 'shortstr'), ('message_id', 'shortstr'), ('timestamp', 'timestamp'), ('type', 'shortstr'), ('user_id', 'shortstr'), ('app_id', 'shortstr'), ('cluster_id', 'shortstr') ] def __init__(self, body='', children=None, channel=None, **properties): """Expected arg types body: string children: (not supported) Keyword properties may include: content_type: shortstr MIME content type content_encoding: shortstr MIME content encoding application_headers: table Message header field table, a dict with string keys, and string | int | Decimal | datetime | dict values. delivery_mode: octet Non-persistent (1) or persistent (2) priority: octet The message priority, 0 to 9 correlation_id: shortstr The application correlation identifier reply_to: shortstr The destination to reply to expiration: shortstr Message expiration specification message_id: shortstr The application message identifier timestamp: datetime.datetime The message timestamp type: shortstr The message type name user_id: shortstr The creating user id app_id: shortstr The creating application id cluster_id: shortstr Intra-cluster routing identifier Unicode bodies are encoded according to the 'content_encoding' argument. If that's None, it's set to 'UTF-8' automatically. example:: msg = Message('hello world', content_type='text/plain', application_headers={'foo': 7}) """ super(Message, self).__init__(**properties) self.body = body self.channel = channel def __eq__(self, other): """Check if the properties and bodies of this Message and another Message are the same. Received messages may contain a 'delivery_info' attribute, which isn't compared. """ try: return (super(Message, self).__eq__(other) and self.body == other.body) except AttributeError: return NotImplemented amqp-1.4.9/amqp/channel.py0000644000076500000000000024441412644026054016031 0ustar asksolwheel00000000000000"""AMQP 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 from __future__ import absolute_import import logging from collections import defaultdict from warnings import warn from .abstract_channel import AbstractChannel from .exceptions import ( ChannelError, ConsumerCancelled, NotConfirmed, error_for_code, ) from .five import Queue from .protocol import basic_return_t, queue_declare_ok_t from .serialization import AMQPWriter __all__ = ['Channel'] AMQP_LOGGER = logging.getLogger('amqp') EXCHANGE_AUTODELETE_DEPRECATED = """\ The auto_delete flag for exchanges has been deprecated and will be removed from py-amqp v1.5.0.\ """ class VDeprecationWarning(DeprecationWarning): pass class Channel(AbstractChannel): """Work with channels The channel class provides methods for a client to establish a virtual connection - a channel - to a server and for both peers to operate the virtual connection thereafter. GRAMMAR:: channel = open-channel *use-channel close-channel open-channel = C:OPEN S:OPEN-OK use-channel = C:FLOW S:FLOW-OK / S:FLOW C:FLOW-OK / functional-class close-channel = C:CLOSE S:CLOSE-OK / S:CLOSE C:CLOSE-OK """ def __init__(self, connection, channel_id=None, auto_decode=True): """Create a channel bound to a connection and using the specified numeric channel_id, and open on the server. The 'auto_decode' parameter (defaults to True), indicates whether the library should attempt to decode the body of Messages to a Unicode string if there's a 'content_encoding' property for the message. If there's no 'content_encoding' property, or the decode raises an Exception, the message body is left as plain bytes. """ if channel_id: connection._claim_channel_id(channel_id) else: channel_id = connection._get_free_channel_id() AMQP_LOGGER.debug('using channel_id: %d', channel_id) super(Channel, self).__init__(connection, channel_id) self.is_open = False self.active = True # Flow control self.returned_messages = Queue() self.callbacks = {} self.cancel_callbacks = {} self.auto_decode = auto_decode self.events = defaultdict(set) self.no_ack_consumers = set() # set first time basic_publish_confirm is called # and publisher confirms are enabled for this channel. self._confirm_selected = False if self.connection.confirm_publish: self.basic_publish = self.basic_publish_confirm self._x_open() def _do_close(self): """Tear down this object, after we've agreed to close with the server.""" AMQP_LOGGER.debug('Closed channel #%d', self.channel_id) self.is_open = False channel_id, self.channel_id = self.channel_id, None connection, self.connection = self.connection, None if connection: connection.channels.pop(channel_id, None) connection._avail_channel_ids.append(channel_id) self.callbacks.clear() self.cancel_callbacks.clear() self.events.clear() self.no_ack_consumers.clear() def _do_revive(self): self.is_open = False self._x_open() def close(self, reply_code=0, reply_text='', method_sig=(0, 0)): """Request a channel close This method indicates that the sender wants to close the channel. This may be due to internal conditions (e.g. a forced shut-down) or due to an error handling a specific method, i.e. an exception. When a close is due to an exception, the sender provides the class and method id of the method which caused the exception. RULE: After sending this method any received method except Channel.Close-OK MUST be discarded. RULE: The peer sending this method MAY use a counter or timeout to detect failure of the other peer to respond correctly with Channel.Close-OK.. PARAMETERS: reply_code: short The reply code. The AMQ reply codes are defined in AMQ RFC 011. reply_text: shortstr The localised reply text. This text can be logged as an aid to resolving issues. class_id: short failing method class When the close is provoked by a method exception, this is the class of the method. method_id: short failing method ID When the close is provoked by a method exception, this is the ID of the method. """ try: if not self.is_open or self.connection is None: return args = AMQPWriter() args.write_short(reply_code) args.write_shortstr(reply_text) args.write_short(method_sig[0]) # class_id args.write_short(method_sig[1]) # method_id self._send_method((20, 40), args) return self.wait(allowed_methods=[ (20, 40), # Channel.close (20, 41), # Channel.close_ok ]) finally: self.connection = None def _close(self, args): """Request a channel close This method indicates that the sender wants to close the channel. This may be due to internal conditions (e.g. a forced shut-down) or due to an error handling a specific method, i.e. an exception. When a close is due to an exception, the sender provides the class and method id of the method which caused the exception. RULE: After sending this method any received method except Channel.Close-OK MUST be discarded. RULE: The peer sending this method MAY use a counter or timeout to detect failure of the other peer to respond correctly with Channel.Close-OK.. PARAMETERS: reply_code: short The reply code. The AMQ reply codes are defined in AMQ RFC 011. reply_text: shortstr The localised reply text. This text can be logged as an aid to resolving issues. class_id: short failing method class When the close is provoked by a method exception, this is the class of the method. method_id: short failing method ID When the close is provoked by a method exception, this is the ID of the method. """ reply_code = args.read_short() reply_text = args.read_shortstr() class_id = args.read_short() method_id = args.read_short() self._send_method((20, 41)) self._do_revive() raise error_for_code( reply_code, reply_text, (class_id, method_id), ChannelError, ) def _close_ok(self, args): """Confirm a channel close This method confirms a Channel.Close method and tells the recipient that it is safe to release resources for the channel and close the socket. RULE: A peer that detects a socket closure without having received a Channel.Close-Ok handshake method SHOULD log the error. """ self._do_close() def flow(self, active): """Enable/disable flow from peer This method asks the peer to pause or restart the flow of content data. This is a simple flow-control mechanism that a peer can use to avoid oveflowing its queues or otherwise finding itself receiving more messages than it can process. Note that this method is not intended for window control. The peer that receives a request to stop sending content should finish sending the current content, if any, and then wait until it receives a Flow restart method. RULE: When a new channel is opened, it is active. Some applications assume that channels are inactive until started. To emulate this behaviour a client MAY open the channel, then pause it. RULE: When sending content data in multiple frames, a peer SHOULD monitor the channel for incoming methods and respond to a Channel.Flow as rapidly as possible. RULE: A peer MAY use the Channel.Flow method to throttle incoming content data for internal reasons, for example, when exchangeing data over a slower connection. RULE: The peer that requests a Channel.Flow method MAY disconnect and/or ban a peer that does not respect the request. PARAMETERS: active: boolean start/stop content frames If True, the peer starts sending content frames. If False, the peer stops sending content frames. """ args = AMQPWriter() args.write_bit(active) self._send_method((20, 20), args) return self.wait(allowed_methods=[ (20, 21), # Channel.flow_ok ]) def _flow(self, args): """Enable/disable flow from peer This method asks the peer to pause or restart the flow of content data. This is a simple flow-control mechanism that a peer can use to avoid oveflowing its queues or otherwise finding itself receiving more messages than it can process. Note that this method is not intended for window control. The peer that receives a request to stop sending content should finish sending the current content, if any, and then wait until it receives a Flow restart method. RULE: When a new channel is opened, it is active. Some applications assume that channels are inactive until started. To emulate this behaviour a client MAY open the channel, then pause it. RULE: When sending content data in multiple frames, a peer SHOULD monitor the channel for incoming methods and respond to a Channel.Flow as rapidly as possible. RULE: A peer MAY use the Channel.Flow method to throttle incoming content data for internal reasons, for example, when exchangeing data over a slower connection. RULE: The peer that requests a Channel.Flow method MAY disconnect and/or ban a peer that does not respect the request. PARAMETERS: active: boolean start/stop content frames If True, the peer starts sending content frames. If False, the peer stops sending content frames. """ self.active = args.read_bit() self._x_flow_ok(self.active) def _x_flow_ok(self, active): """Confirm a flow method Confirms to the peer that a flow command was received and processed. PARAMETERS: active: boolean current flow setting Confirms the setting of the processed flow method: True means the peer will start sending or continue to send content frames; False means it will not. """ args = AMQPWriter() args.write_bit(active) self._send_method((20, 21), args) def _flow_ok(self, args): """Confirm a flow method Confirms to the peer that a flow command was received and processed. PARAMETERS: active: boolean current flow setting Confirms the setting of the processed flow method: True means the peer will start sending or continue to send content frames; False means it will not. """ return args.read_bit() def _x_open(self): """Open a channel for use This method opens a virtual connection (a channel). RULE: This method MUST NOT be called when the channel is already open. PARAMETERS: out_of_band: shortstr (DEPRECATED) out-of-band settings Configures out-of-band transfers on this channel. The syntax and meaning of this field will be formally defined at a later date. """ if self.is_open: return args = AMQPWriter() args.write_shortstr('') # out_of_band: deprecated self._send_method((20, 10), args) return self.wait(allowed_methods=[ (20, 11), # Channel.open_ok ]) def _open_ok(self, args): """Signal that the channel is ready This method signals to the client that the channel is ready for use. """ self.is_open = True AMQP_LOGGER.debug('Channel open') ############# # # Exchange # # # work with exchanges # # Exchanges match and distribute messages across queues. # Exchanges can be configured in the server or created at runtime. # # GRAMMAR:: # # exchange = C:DECLARE S:DECLARE-OK # / C:DELETE S:DELETE-OK # # RULE: # # The server MUST implement the direct and fanout exchange # types, and predeclare the corresponding exchanges named # amq.direct and amq.fanout in each virtual host. The server # MUST also predeclare a direct exchange to act as the default # exchange for content Publish methods and for default queue # bindings. # # RULE: # # The server SHOULD implement the topic exchange type, and # predeclare the corresponding exchange named amq.topic in # each virtual host. # # RULE: # # The server MAY implement the system exchange type, and # predeclare the corresponding exchanges named amq.system in # each virtual host. If the client attempts to bind a queue to # the system exchange, the server MUST raise a connection # exception with reply code 507 (not allowed). # def exchange_declare(self, exchange, type, passive=False, durable=False, auto_delete=True, nowait=False, arguments=None): """Declare exchange, create if needed This method creates an exchange if it does not already exist, and if the exchange exists, verifies that it is of the correct and expected class. RULE: The server SHOULD support a minimum of 16 exchanges per virtual host and ideally, impose no limit except as defined by available resources. PARAMETERS: exchange: shortstr RULE: Exchange names starting with "amq." are reserved for predeclared and standardised exchanges. If the client attempts to create an exchange starting with "amq.", the server MUST raise a channel exception with reply code 403 (access refused). type: shortstr exchange type Each exchange belongs to one of a set of exchange types implemented by the server. The exchange types define the functionality of the exchange - i.e. how messages are routed through it. It is not valid or meaningful to attempt to change the type of an existing exchange. RULE: If the exchange already exists with a different type, the server MUST raise a connection exception with a reply code 507 (not allowed). RULE: If the server does not support the requested exchange type it MUST raise a connection exception with a reply code 503 (command invalid). passive: boolean do not create exchange If set, the server will not create the exchange. The client can use this to check whether an exchange exists without modifying the server state. RULE: If set, and the exchange does not already exist, the server MUST raise a channel exception with reply code 404 (not found). durable: boolean request a durable exchange If set when creating a new exchange, the exchange will be marked as durable. Durable exchanges remain active when a server restarts. Non-durable exchanges (transient exchanges) are purged if/when a server restarts. RULE: The server MUST support both durable and transient exchanges. RULE: The server MUST ignore the durable field if the exchange already exists. auto_delete: boolean auto-delete when unused If set, the exchange is deleted when all queues have finished using it. RULE: The server SHOULD allow for a reasonable delay between the point when it determines that an exchange is not being used (or no longer used), and the point when it deletes the exchange. At the least it must allow a client to create an exchange and then bind a queue to it, with a small but non-zero delay between these two actions. RULE: The server MUST ignore the auto-delete field if the exchange already exists. nowait: boolean do not send a reply method If set, the server will not respond to the method. The client should not wait for a reply method. If the server could not complete the method it will raise a channel or connection exception. arguments: table arguments for declaration A set of arguments for the declaration. The syntax and semantics of these arguments depends on the server implementation. This field is ignored if passive is True. """ arguments = {} if arguments is None else arguments args = AMQPWriter() args.write_short(0) args.write_shortstr(exchange) args.write_shortstr(type) args.write_bit(passive) args.write_bit(durable) args.write_bit(auto_delete) args.write_bit(False) # internal: deprecated args.write_bit(nowait) args.write_table(arguments) self._send_method((40, 10), args) if auto_delete: warn(VDeprecationWarning(EXCHANGE_AUTODELETE_DEPRECATED)) if not nowait: return self.wait(allowed_methods=[ (40, 11), # Channel.exchange_declare_ok ]) def _exchange_declare_ok(self, args): """Confirms an exchange declaration This method confirms a Declare method and confirms the name of the exchange, essential for automatically-named exchanges. """ pass def exchange_delete(self, exchange, if_unused=False, nowait=False): """Delete an exchange This method deletes an exchange. When an exchange is deleted all queue bindings on the exchange are cancelled. PARAMETERS: exchange: shortstr RULE: The exchange MUST exist. Attempting to delete a non-existing exchange causes a channel exception. if_unused: boolean delete only if unused If set, the server will only delete the exchange if it has no queue bindings. If the exchange has queue bindings the server does not delete it but raises a channel exception instead. RULE: If set, the server SHOULD delete the exchange but only if it has no queue bindings. RULE: If set, the server SHOULD raise a channel exception if the exchange is in use. nowait: boolean do not send a reply method If set, the server will not respond to the method. The client should not wait for a reply method. If the server could not complete the method it will raise a channel or connection exception. """ args = AMQPWriter() args.write_short(0) args.write_shortstr(exchange) args.write_bit(if_unused) args.write_bit(nowait) self._send_method((40, 20), args) if not nowait: return self.wait(allowed_methods=[ (40, 21), # Channel.exchange_delete_ok ]) def _exchange_delete_ok(self, args): """Confirm deletion of an exchange This method confirms the deletion of an exchange. """ pass def exchange_bind(self, destination, source='', routing_key='', nowait=False, arguments=None): """This method binds an exchange to an exchange. RULE: A server MUST allow and ignore duplicate bindings - that is, two or more bind methods for a specific exchanges, with identical arguments - without treating these as an error. RULE: A server MUST allow cycles of exchange bindings to be created including allowing an exchange to be bound to itself. RULE: A server MUST not deliver the same message more than once to a destination exchange, even if the topology of exchanges and bindings results in multiple (even infinite) routes to that exchange. PARAMETERS: reserved-1: short destination: shortstr Specifies the name of the destination exchange to bind. RULE: A client MUST NOT be allowed to bind a non- existent destination exchange. RULE: The server MUST accept a blank exchange name to mean the default exchange. source: shortstr Specifies the name of the source exchange to bind. RULE: A client MUST NOT be allowed to bind a non- existent source exchange. RULE: The server MUST accept a blank exchange name to mean the default exchange. routing-key: shortstr Specifies the routing key for the binding. The routing key is used for routing messages depending on the exchange configuration. Not all exchanges use a routing key - refer to the specific exchange documentation. no-wait: bit arguments: table A set of arguments for the binding. The syntax and semantics of these arguments depends on the exchange class. """ arguments = {} if arguments is None else arguments args = AMQPWriter() args.write_short(0) args.write_shortstr(destination) args.write_shortstr(source) args.write_shortstr(routing_key) args.write_bit(nowait) args.write_table(arguments) self._send_method((40, 30), args) if not nowait: return self.wait(allowed_methods=[ (40, 31), # Channel.exchange_bind_ok ]) def exchange_unbind(self, destination, source='', routing_key='', nowait=False, arguments=None): """This method unbinds an exchange from an exchange. RULE: If a unbind fails, the server MUST raise a connection exception. PARAMETERS: reserved-1: short destination: shortstr Specifies the name of the destination exchange to unbind. RULE: The client MUST NOT attempt to unbind an exchange that does not exist from an exchange. RULE: The server MUST accept a blank exchange name to mean the default exchange. source: shortstr Specifies the name of the source exchange to unbind. RULE: The client MUST NOT attempt to unbind an exchange from an exchange that does not exist. RULE: The server MUST accept a blank exchange name to mean the default exchange. routing-key: shortstr Specifies the routing key of the binding to unbind. no-wait: bit arguments: table Specifies the arguments of the binding to unbind. """ arguments = {} if arguments is None else arguments args = AMQPWriter() args.write_short(0) args.write_shortstr(destination) args.write_shortstr(source) args.write_shortstr(routing_key) args.write_bit(nowait) args.write_table(arguments) self._send_method((40, 40), args) if not nowait: return self.wait(allowed_methods=[ (40, 51), # Channel.exchange_unbind_ok ]) def _exchange_bind_ok(self, args): """Confirm bind successful This method confirms that the bind was successful. """ pass def _exchange_unbind_ok(self, args): """Confirm unbind successful This method confirms that the unbind was successful. """ pass ############# # # Queue # # # work with queues # # Queues store and forward messages. Queues can be configured in # the server or created at runtime. Queues must be attached to at # least one exchange in order to receive messages from publishers. # # GRAMMAR:: # # queue = C:DECLARE S:DECLARE-OK # / C:BIND S:BIND-OK # / C:PURGE S:PURGE-OK # / C:DELETE S:DELETE-OK # # RULE: # # A server MUST allow any content class to be sent to any # queue, in any mix, and queue and delivery these content # classes independently. Note that all methods that fetch # content off queues are specific to a given content class. # def queue_bind(self, queue, exchange='', routing_key='', nowait=False, arguments=None): """Bind queue to an exchange This method binds a queue to an exchange. Until a queue is bound it will not receive any messages. In a classic messaging model, store-and-forward queues are bound to a dest exchange and subscription queues are bound to a dest_wild exchange. RULE: A server MUST allow ignore duplicate bindings - that is, two or more bind methods for a specific queue, with identical arguments - without treating these as an error. RULE: If a bind fails, the server MUST raise a connection exception. RULE: The server MUST NOT allow a durable queue to bind to a transient exchange. If the client attempts this the server MUST raise a channel exception. RULE: Bindings for durable queues are automatically durable and the server SHOULD restore such bindings after a server restart. RULE: The server SHOULD support at least 4 bindings per queue, and ideally, impose no limit except as defined by available resources. PARAMETERS: queue: shortstr Specifies the name of the queue to bind. If the queue name is empty, refers to the current queue for the channel, which is the last declared queue. RULE: If the client did not previously declare a queue, and the queue name in this method is empty, the server MUST raise a connection exception with reply code 530 (not allowed). RULE: If the queue does not exist the server MUST raise a channel exception with reply code 404 (not found). exchange: shortstr The name of the exchange to bind to. RULE: If the exchange does not exist the server MUST raise a channel exception with reply code 404 (not found). routing_key: shortstr message routing key Specifies the routing key for the binding. The routing key is used for routing messages depending on the exchange configuration. Not all exchanges use a routing key - refer to the specific exchange documentation. If the routing key is empty and the queue name is empty, the routing key will be the current queue for the channel, which is the last declared queue. nowait: boolean do not send a reply method If set, the server will not respond to the method. The client should not wait for a reply method. If the server could not complete the method it will raise a channel or connection exception. arguments: table arguments for binding A set of arguments for the binding. The syntax and semantics of these arguments depends on the exchange class. """ arguments = {} if arguments is None else arguments args = AMQPWriter() args.write_short(0) args.write_shortstr(queue) args.write_shortstr(exchange) args.write_shortstr(routing_key) args.write_bit(nowait) args.write_table(arguments) self._send_method((50, 20), args) if not nowait: return self.wait(allowed_methods=[ (50, 21), # Channel.queue_bind_ok ]) def _queue_bind_ok(self, args): """Confirm bind successful This method confirms that the bind was successful. """ pass def queue_unbind(self, queue, exchange, routing_key='', nowait=False, arguments=None): """Unbind a queue from an exchange This method unbinds a queue from an exchange. RULE: If a unbind fails, the server MUST raise a connection exception. PARAMETERS: queue: shortstr Specifies the name of the queue to unbind. RULE: The client MUST either specify a queue name or have previously declared a queue on the same channel RULE: The client MUST NOT attempt to unbind a queue that does not exist. exchange: shortstr The name of the exchange to unbind from. RULE: The client MUST NOT attempt to unbind a queue from an exchange that does not exist. RULE: The server MUST accept a blank exchange name to mean the default exchange. routing_key: shortstr routing key of binding Specifies the routing key of the binding to unbind. arguments: table arguments of binding Specifies the arguments of the binding to unbind. """ arguments = {} if arguments is None else arguments args = AMQPWriter() args.write_short(0) args.write_shortstr(queue) args.write_shortstr(exchange) args.write_shortstr(routing_key) args.write_table(arguments) self._send_method((50, 50), args) if not nowait: return self.wait(allowed_methods=[ (50, 51), # Channel.queue_unbind_ok ]) def _queue_unbind_ok(self, args): """Confirm unbind successful This method confirms that the unbind was successful. """ pass def queue_declare(self, queue='', passive=False, durable=False, exclusive=False, auto_delete=True, nowait=False, arguments=None): """Declare queue, create if needed This method creates or checks a queue. When creating a new queue the client can specify various properties that control the durability of the queue and its contents, and the level of sharing for the queue. RULE: The server MUST create a default binding for a newly- created queue to the default exchange, which is an exchange of type 'direct'. RULE: The server SHOULD support a minimum of 256 queues per virtual host and ideally, impose no limit except as defined by available resources. PARAMETERS: queue: shortstr RULE: The queue name MAY be empty, in which case the server MUST create a new queue with a unique generated name and return this to the client in the Declare-Ok method. RULE: Queue names starting with "amq." are reserved for predeclared and standardised server queues. If the queue name starts with "amq." and the passive option is False, the server MUST raise a connection exception with reply code 403 (access refused). passive: boolean do not create queue If set, the server will not create the queue. The client can use this to check whether a queue exists without modifying the server state. RULE: If set, and the queue does not already exist, the server MUST respond with a reply code 404 (not found) and raise a channel exception. durable: boolean request a durable queue If set when creating a new queue, the queue will be marked as durable. Durable queues remain active when a server restarts. Non-durable queues (transient queues) are purged if/when a server restarts. Note that durable queues do not necessarily hold persistent messages, although it does not make sense to send persistent messages to a transient queue. RULE: The server MUST recreate the durable queue after a restart. RULE: The server MUST support both durable and transient queues. RULE: The server MUST ignore the durable field if the queue already exists. exclusive: boolean request an exclusive queue Exclusive queues may only be consumed from by the current connection. Setting the 'exclusive' flag always implies 'auto-delete'. RULE: The server MUST support both exclusive (private) and non-exclusive (shared) queues. RULE: The server MUST raise a channel exception if 'exclusive' is specified and the queue already exists and is owned by a different connection. auto_delete: boolean auto-delete queue when unused If set, the queue is deleted when all consumers have finished using it. Last consumer can be cancelled either explicitly or because its channel is closed. If there was no consumer ever on the queue, it won't be deleted. RULE: The server SHOULD allow for a reasonable delay between the point when it determines that a queue is not being used (or no longer used), and the point when it deletes the queue. At the least it must allow a client to create a queue and then create a consumer to read from it, with a small but non-zero delay between these two actions. The server should equally allow for clients that may be disconnected prematurely, and wish to re- consume from the same queue without losing messages. We would recommend a configurable timeout, with a suitable default value being one minute. RULE: The server MUST ignore the auto-delete field if the queue already exists. nowait: boolean do not send a reply method If set, the server will not respond to the method. The client should not wait for a reply method. If the server could not complete the method it will raise a channel or connection exception. arguments: table arguments for declaration A set of arguments for the declaration. The syntax and semantics of these arguments depends on the server implementation. This field is ignored if passive is True. Returns a tuple containing 3 items: the name of the queue (essential for automatically-named queues) message count consumer count """ arguments = {} if arguments is None else arguments args = AMQPWriter() args.write_short(0) args.write_shortstr(queue) args.write_bit(passive) args.write_bit(durable) args.write_bit(exclusive) args.write_bit(auto_delete) args.write_bit(nowait) args.write_table(arguments) self._send_method((50, 10), args) if not nowait: return self.wait(allowed_methods=[ (50, 11), # Channel.queue_declare_ok ]) def _queue_declare_ok(self, args): """Confirms a queue definition This method confirms a Declare method and confirms the name of the queue, essential for automatically-named queues. PARAMETERS: queue: shortstr Reports the name of the queue. If the server generated a queue name, this field contains that name. message_count: long number of messages in queue Reports the number of messages in the queue, which will be zero for newly-created queues. consumer_count: long number of consumers Reports the number of active consumers for the queue. Note that consumers can suspend activity (Channel.Flow) in which case they do not appear in this count. """ return queue_declare_ok_t( args.read_shortstr(), args.read_long(), args.read_long(), ) def queue_delete(self, queue='', if_unused=False, if_empty=False, nowait=False): """Delete a queue This method deletes a queue. When a queue is deleted any pending messages are sent to a dead-letter queue if this is defined in the server configuration, and all consumers on the queue are cancelled. RULE: The server SHOULD use a dead-letter queue to hold messages that were pending on a deleted queue, and MAY provide facilities for a system administrator to move these messages back to an active queue. PARAMETERS: queue: shortstr Specifies the name of the queue to delete. If the queue name is empty, refers to the current queue for the channel, which is the last declared queue. RULE: If the client did not previously declare a queue, and the queue name in this method is empty, the server MUST raise a connection exception with reply code 530 (not allowed). RULE: The queue must exist. Attempting to delete a non- existing queue causes a channel exception. if_unused: boolean delete only if unused If set, the server will only delete the queue if it has no consumers. If the queue has consumers the server does does not delete it but raises a channel exception instead. RULE: The server MUST respect the if-unused flag when deleting a queue. if_empty: boolean delete only if empty If set, the server will only delete the queue if it has no messages. If the queue is not empty the server raises a channel exception. nowait: boolean do not send a reply method If set, the server will not respond to the method. The client should not wait for a reply method. If the server could not complete the method it will raise a channel or connection exception. """ args = AMQPWriter() args.write_short(0) args.write_shortstr(queue) args.write_bit(if_unused) args.write_bit(if_empty) args.write_bit(nowait) self._send_method((50, 40), args) if not nowait: return self.wait(allowed_methods=[ (50, 41), # Channel.queue_delete_ok ]) def _queue_delete_ok(self, args): """Confirm deletion of a queue This method confirms the deletion of a queue. PARAMETERS: message_count: long number of messages purged Reports the number of messages purged. """ return args.read_long() def queue_purge(self, queue='', nowait=False): """Purge a queue This method removes all messages from a queue. It does not cancel consumers. Purged messages are deleted without any formal "undo" mechanism. RULE: A call to purge MUST result in an empty queue. RULE: On transacted channels the server MUST not purge messages that have already been sent to a client but not yet acknowledged. RULE: The server MAY implement a purge queue or log that allows system administrators to recover accidentally-purged messages. The server SHOULD NOT keep purged messages in the same storage spaces as the live messages since the volumes of purged messages may get very large. PARAMETERS: queue: shortstr Specifies the name of the queue to purge. If the queue name is empty, refers to the current queue for the channel, which is the last declared queue. RULE: If the client did not previously declare a queue, and the queue name in this method is empty, the server MUST raise a connection exception with reply code 530 (not allowed). RULE: The queue must exist. Attempting to purge a non- existing queue causes a channel exception. nowait: boolean do not send a reply method If set, the server will not respond to the method. The client should not wait for a reply method. If the server could not complete the method it will raise a channel or connection exception. if nowait is False, returns a message_count """ args = AMQPWriter() args.write_short(0) args.write_shortstr(queue) args.write_bit(nowait) self._send_method((50, 30), args) if not nowait: return self.wait(allowed_methods=[ (50, 31), # Channel.queue_purge_ok ]) def _queue_purge_ok(self, args): """Confirms a queue purge This method confirms the purge of a queue. PARAMETERS: message_count: long number of messages purged Reports the number of messages purged. """ return args.read_long() ############# # # Basic # # # work with basic content # # The Basic class provides methods that support an industry- # standard messaging model. # # GRAMMAR:: # # basic = C:QOS S:QOS-OK # / C:CONSUME S:CONSUME-OK # / C:CANCEL S:CANCEL-OK # / C:PUBLISH content # / S:RETURN content # / S:DELIVER content # / C:GET ( S:GET-OK content / S:GET-EMPTY ) # / C:ACK # / C:REJECT # # RULE: # # The server SHOULD respect the persistent property of basic # messages and SHOULD make a best-effort to hold persistent # basic messages on a reliable storage mechanism. # # RULE: # # The server MUST NOT discard a persistent basic message in # case of a queue overflow. The server MAY use the # Channel.Flow method to slow or stop a basic message # publisher when necessary. # # RULE: # # The server MAY overflow non-persistent basic messages to # persistent storage and MAY discard or dead-letter non- # persistent basic messages on a priority basis if the queue # size exceeds some configured limit. # # RULE: # # The server MUST implement at least 2 priority levels for # basic messages, where priorities 0-4 and 5-9 are treated as # two distinct levels. The server MAY implement up to 10 # priority levels. # # RULE: # # The server MUST deliver messages of the same priority in # order irrespective of their individual persistence. # # RULE: # # The server MUST support both automatic and explicit # acknowledgements on Basic content. # def basic_ack(self, delivery_tag, multiple=False): """Acknowledge one or more messages This method acknowledges one or more messages delivered via the Deliver or Get-Ok methods. The client can ask to confirm a single message or a set of messages up to and including a specific message. PARAMETERS: delivery_tag: longlong server-assigned delivery tag The server-assigned and channel-specific delivery tag RULE: The delivery tag is valid only within the channel from which the message was received. I.e. a client MUST NOT receive a message on one channel and then acknowledge it on another. RULE: The server MUST NOT use a zero value for delivery tags. Zero is reserved for client use, meaning "all messages so far received". multiple: boolean acknowledge multiple messages If set to True, the delivery tag is treated as "up to and including", so that the client can acknowledge multiple messages with a single method. If set to False, the delivery tag refers to a single message. If the multiple field is True, and the delivery tag is zero, tells the server to acknowledge all outstanding mesages. RULE: The server MUST validate that a non-zero delivery- tag refers to an delivered message, and raise a channel exception if this is not the case. """ args = AMQPWriter() args.write_longlong(delivery_tag) args.write_bit(multiple) self._send_method((60, 80), args) def basic_cancel(self, consumer_tag, nowait=False): """End a queue consumer This method cancels a consumer. This does not affect already delivered messages, but it does mean the server will not send any more messages for that consumer. The client may receive an abitrary number of messages in between sending the cancel method and receiving the cancel-ok reply. RULE: If the queue no longer exists when the client sends a cancel command, or the consumer has been cancelled for other reasons, this command has no effect. PARAMETERS: consumer_tag: shortstr consumer tag Identifier for the consumer, valid within the current connection. RULE: The consumer tag is valid only within the channel from which the consumer was created. I.e. a client MUST NOT create a consumer in one channel and then use it in another. nowait: boolean do not send a reply method If set, the server will not respond to the method. The client should not wait for a reply method. If the server could not complete the method it will raise a channel or connection exception. """ if self.connection is not None: self.no_ack_consumers.discard(consumer_tag) args = AMQPWriter() args.write_shortstr(consumer_tag) args.write_bit(nowait) self._send_method((60, 30), args) return self.wait(allowed_methods=[ (60, 31), # Channel.basic_cancel_ok ]) def _basic_cancel_notify(self, args): """Consumer cancelled by server. Most likely the queue was deleted. """ consumer_tag = args.read_shortstr() callback = self._on_cancel(consumer_tag) if callback: callback(consumer_tag) else: raise ConsumerCancelled(consumer_tag, (60, 30)) def _basic_cancel_ok(self, args): """Confirm a cancelled consumer This method confirms that the cancellation was completed. PARAMETERS: consumer_tag: shortstr consumer tag Identifier for the consumer, valid within the current connection. RULE: The consumer tag is valid only within the channel from which the consumer was created. I.e. a client MUST NOT create a consumer in one channel and then use it in another. """ consumer_tag = args.read_shortstr() self._on_cancel(consumer_tag) def _on_cancel(self, consumer_tag): self.callbacks.pop(consumer_tag, None) return self.cancel_callbacks.pop(consumer_tag, None) def basic_consume(self, queue='', consumer_tag='', no_local=False, no_ack=False, exclusive=False, nowait=False, callback=None, arguments=None, on_cancel=None): """Start a queue consumer This method asks the server to start a "consumer", which is a transient request for messages from a specific queue. Consumers last as long as the channel they were created on, or until the client cancels them. RULE: The server SHOULD support at least 16 consumers per queue, unless the queue was declared as private, and ideally, impose no limit except as defined by available resources. PARAMETERS: queue: shortstr Specifies the name of the queue to consume from. If the queue name is null, refers to the current queue for the channel, which is the last declared queue. RULE: If the client did not previously declare a queue, and the queue name in this method is empty, the server MUST raise a connection exception with reply code 530 (not allowed). consumer_tag: shortstr Specifies the identifier for the consumer. The consumer tag is local to a connection, so two clients can use the same consumer tags. If this field is empty the server will generate a unique tag. RULE: The tag MUST NOT refer to an existing consumer. If the client attempts to create two consumers with the same non-empty tag the server MUST raise a connection exception with reply code 530 (not allowed). no_local: boolean do not deliver own messages If the no-local field is set the server will not send messages to the client that published them. no_ack: boolean no acknowledgement needed If this field is set the server does not expect acknowledgments for messages. That is, when a message is delivered to the client the server automatically and silently acknowledges it on behalf of the client. This functionality increases performance but at the cost of reliability. Messages can get lost if a client dies before it can deliver them to the application. exclusive: boolean request exclusive access Request exclusive consumer access, meaning only this consumer can access the queue. RULE: If the server cannot grant exclusive access to the queue when asked, - because there are other consumers active - it MUST raise a channel exception with return code 403 (access refused). nowait: boolean do not send a reply method If set, the server will not respond to the method. The client should not wait for a reply method. If the server could not complete the method it will raise a channel or connection exception. callback: Python callable function/method called with each delivered message For each message delivered by the broker, the callable will be called with a Message object as the single argument. If no callable is specified, messages are quietly discarded, no_ack should probably be set to True in that case. """ args = AMQPWriter() args.write_short(0) args.write_shortstr(queue) args.write_shortstr(consumer_tag) args.write_bit(no_local) args.write_bit(no_ack) args.write_bit(exclusive) args.write_bit(nowait) args.write_table(arguments or {}) self._send_method((60, 20), args) if not nowait: consumer_tag = self.wait(allowed_methods=[ (60, 21), # Channel.basic_consume_ok ]) self.callbacks[consumer_tag] = callback if on_cancel: self.cancel_callbacks[consumer_tag] = on_cancel if no_ack: self.no_ack_consumers.add(consumer_tag) return consumer_tag def _basic_consume_ok(self, args): """Confirm a new consumer The server provides the client with a consumer tag, which is used by the client for methods called on the consumer at a later stage. PARAMETERS: consumer_tag: shortstr Holds the consumer tag specified by the client or provided by the server. """ return args.read_shortstr() def _basic_deliver(self, args, msg): """Notify the client of a consumer message This method delivers a message to the client, via a consumer. In the asynchronous message delivery model, the client starts a consumer using the Consume method, then the server responds with Deliver methods as and when messages arrive for that consumer. RULE: The server SHOULD track the number of times a message has been delivered to clients and when a message is redelivered a certain number of times - e.g. 5 times - without being acknowledged, the server SHOULD consider the message to be unprocessable (possibly causing client applications to abort), and move the message to a dead letter queue. PARAMETERS: consumer_tag: shortstr consumer tag Identifier for the consumer, valid within the current connection. RULE: The consumer tag is valid only within the channel from which the consumer was created. I.e. a client MUST NOT create a consumer in one channel and then use it in another. delivery_tag: longlong server-assigned delivery tag The server-assigned and channel-specific delivery tag RULE: The delivery tag is valid only within the channel from which the message was received. I.e. a client MUST NOT receive a message on one channel and then acknowledge it on another. RULE: The server MUST NOT use a zero value for delivery tags. Zero is reserved for client use, meaning "all messages so far received". redelivered: boolean message is being redelivered This indicates that the message has been previously delivered to this or another client. exchange: shortstr Specifies the name of the exchange that the message was originally published to. routing_key: shortstr Message routing key Specifies the routing key name specified when the message was published. """ consumer_tag = args.read_shortstr() delivery_tag = args.read_longlong() redelivered = args.read_bit() exchange = args.read_shortstr() routing_key = args.read_shortstr() msg.channel = self msg.delivery_info = { 'consumer_tag': consumer_tag, 'delivery_tag': delivery_tag, 'redelivered': redelivered, 'exchange': exchange, 'routing_key': routing_key, } try: fun = self.callbacks[consumer_tag] except KeyError: pass else: fun(msg) def basic_get(self, queue='', no_ack=False): """Direct access to a queue This method provides a direct access to the messages in a queue using a synchronous dialogue that is designed for specific types of application where synchronous functionality is more important than performance. PARAMETERS: queue: shortstr Specifies the name of the queue to consume from. If the queue name is null, refers to the current queue for the channel, which is the last declared queue. RULE: If the client did not previously declare a queue, and the queue name in this method is empty, the server MUST raise a connection exception with reply code 530 (not allowed). no_ack: boolean no acknowledgement needed If this field is set the server does not expect acknowledgments for messages. That is, when a message is delivered to the client the server automatically and silently acknowledges it on behalf of the client. This functionality increases performance but at the cost of reliability. Messages can get lost if a client dies before it can deliver them to the application. Non-blocking, returns a message object, or None. """ args = AMQPWriter() args.write_short(0) args.write_shortstr(queue) args.write_bit(no_ack) self._send_method((60, 70), args) return self.wait(allowed_methods=[ (60, 71), # Channel.basic_get_ok (60, 72), # Channel.basic_get_empty ]) def _basic_get_empty(self, args): """Indicate no messages available This method tells the client that the queue has no messages available for the client. PARAMETERS: cluster_id: shortstr Cluster id For use by cluster applications, should not be used by client applications. """ cluster_id = args.read_shortstr() # noqa def _basic_get_ok(self, args, msg): """Provide client with a message This method delivers a message to the client following a get method. A message delivered by 'get-ok' must be acknowledged unless the no-ack option was set in the get method. PARAMETERS: delivery_tag: longlong server-assigned delivery tag The server-assigned and channel-specific delivery tag RULE: The delivery tag is valid only within the channel from which the message was received. I.e. a client MUST NOT receive a message on one channel and then acknowledge it on another. RULE: The server MUST NOT use a zero value for delivery tags. Zero is reserved for client use, meaning "all messages so far received". redelivered: boolean message is being redelivered This indicates that the message has been previously delivered to this or another client. exchange: shortstr Specifies the name of the exchange that the message was originally published to. If empty, the message was published to the default exchange. routing_key: shortstr Message routing key Specifies the routing key name specified when the message was published. message_count: long number of messages pending This field reports the number of messages pending on the queue, excluding the message being delivered. Note that this figure is indicative, not reliable, and can change arbitrarily as messages are added to the queue and removed by other clients. """ delivery_tag = args.read_longlong() redelivered = args.read_bit() exchange = args.read_shortstr() routing_key = args.read_shortstr() message_count = args.read_long() msg.channel = self msg.delivery_info = { 'delivery_tag': delivery_tag, 'redelivered': redelivered, 'exchange': exchange, 'routing_key': routing_key, 'message_count': message_count } return msg def _basic_publish(self, msg, exchange='', routing_key='', mandatory=False, immediate=False): """Publish a message This method publishes a message to a specific exchange. The message will be routed to queues as defined by the exchange configuration and distributed to any active consumers when the transaction, if any, is committed. PARAMETERS: exchange: shortstr Specifies the name of the exchange to publish to. The exchange name can be empty, meaning the default exchange. If the exchange name is specified, and that exchange does not exist, the server will raise a channel exception. RULE: The server MUST accept a blank exchange name to mean the default exchange. RULE: The exchange MAY refuse basic content in which case it MUST raise a channel exception with reply code 540 (not implemented). routing_key: shortstr Message routing key Specifies the routing key for the message. The routing key is used for routing messages depending on the exchange configuration. mandatory: boolean indicate mandatory routing This flag tells the server how to react if the message cannot be routed to a queue. If this flag is True, the server will return an unroutable message with a Return method. If this flag is False, the server silently drops the message. RULE: The server SHOULD implement the mandatory flag. immediate: boolean request immediate delivery This flag tells the server how to react if the message cannot be routed to a queue consumer immediately. If this flag is set, the server will return an undeliverable message with a Return method. If this flag is zero, the server will queue the message, but with no guarantee that it will ever be consumed. RULE: The server SHOULD implement the immediate flag. """ args = AMQPWriter() args.write_short(0) args.write_shortstr(exchange) args.write_shortstr(routing_key) args.write_bit(mandatory) args.write_bit(immediate) self._send_method((60, 40), args, msg) basic_publish = _basic_publish def basic_publish_confirm(self, *args, **kwargs): if not self._confirm_selected: self._confirm_selected = True self.confirm_select() ret = self._basic_publish(*args, **kwargs) # Basic.Ack / Basic.Nack self.wait([(60, 80), (60, 120)]) return ret def basic_qos(self, prefetch_size, prefetch_count, a_global): """Specify quality of service This method requests a specific quality of service. The QoS can be specified for the current channel or for all channels on the connection. The particular properties and semantics of a qos method always depend on the content class semantics. Though the qos method could in principle apply to both peers, it is currently meaningful only for the server. PARAMETERS: prefetch_size: long prefetch window in octets The client can request that messages be sent in advance so that when the client finishes processing a message, the following message is already held locally, rather than needing to be sent down the channel. Prefetching gives a performance improvement. This field specifies the prefetch window size in octets. The server will send a message in advance if it is equal to or smaller in size than the available prefetch size (and also falls into other prefetch limits). May be set to zero, meaning "no specific limit", although other prefetch limits may still apply. The prefetch-size is ignored if the no-ack option is set. RULE: The server MUST ignore this setting when the client is not processing any messages - i.e. the prefetch size does not limit the transfer of single messages to a client, only the sending in advance of more messages while the client still has one or more unacknowledged messages. prefetch_count: short prefetch window in messages Specifies a prefetch window in terms of whole messages. This field may be used in combination with the prefetch-size field; a message will only be sent in advance if both prefetch windows (and those at the channel and connection level) allow it. The prefetch- count is ignored if the no-ack option is set. RULE: The server MAY send less data in advance than allowed by the client's specified prefetch windows but it MUST NOT send more. a_global: boolean apply to entire connection By default the QoS settings apply to the current channel only. If this field is set, they are applied to the entire connection. """ args = AMQPWriter() args.write_long(prefetch_size) args.write_short(prefetch_count) args.write_bit(a_global) self._send_method((60, 10), args) return self.wait(allowed_methods=[ (60, 11), # Channel.basic_qos_ok ]) def _basic_qos_ok(self, args): """Confirm the requested qos This method tells the client that the requested QoS levels could be handled by the server. The requested QoS applies to all active consumers until a new QoS is defined. """ pass def basic_recover(self, requeue=False): """Redeliver unacknowledged messages This method asks the broker to redeliver all unacknowledged messages on a specified channel. Zero or more messages may be redelivered. This method is only allowed on non-transacted channels. RULE: The server MUST set the redelivered flag on all messages that are resent. RULE: The server MUST raise a channel exception if this is called on a transacted channel. PARAMETERS: requeue: boolean requeue the message If this field is False, the message will be redelivered to the original recipient. If this field is True, the server will attempt to requeue the message, potentially then delivering it to an alternative subscriber. """ args = AMQPWriter() args.write_bit(requeue) self._send_method((60, 110), args) def basic_recover_async(self, requeue=False): args = AMQPWriter() args.write_bit(requeue) self._send_method((60, 100), args) def _basic_recover_ok(self, args): """In 0-9-1 the deprecated recover solicits a response.""" pass def basic_reject(self, delivery_tag, requeue): """Reject an incoming message This method allows a client to reject a message. It can be used to interrupt and cancel large incoming messages, or return untreatable messages to their original queue. RULE: The server SHOULD be capable of accepting and process the Reject method while sending message content with a Deliver or Get-Ok method. I.e. the server should read and process incoming methods while sending output frames. To cancel a partially-send content, the server sends a content body frame of size 1 (i.e. with no data except the frame-end octet). RULE: The server SHOULD interpret this method as meaning that the client is unable to process the message at this time. RULE: A client MUST NOT use this method as a means of selecting messages to process. A rejected message MAY be discarded or dead-lettered, not necessarily passed to another client. PARAMETERS: delivery_tag: longlong server-assigned delivery tag The server-assigned and channel-specific delivery tag RULE: The delivery tag is valid only within the channel from which the message was received. I.e. a client MUST NOT receive a message on one channel and then acknowledge it on another. RULE: The server MUST NOT use a zero value for delivery tags. Zero is reserved for client use, meaning "all messages so far received". requeue: boolean requeue the message If this field is False, the message will be discarded. If this field is True, the server will attempt to requeue the message. RULE: The server MUST NOT deliver the message to the same client within the context of the current channel. The recommended strategy is to attempt to deliver the message to an alternative consumer, and if that is not possible, to move the message to a dead-letter queue. The server MAY use more sophisticated tracking to hold the message on the queue and redeliver it to the same client at a later stage. """ args = AMQPWriter() args.write_longlong(delivery_tag) args.write_bit(requeue) self._send_method((60, 90), args) def _basic_return(self, args, msg): """Return a failed message This method returns an undeliverable message that was published with the "immediate" flag set, or an unroutable message published with the "mandatory" flag set. The reply code and text provide information about the reason that the message was undeliverable. PARAMETERS: reply_code: short The reply code. The AMQ reply codes are defined in AMQ RFC 011. reply_text: shortstr The localised reply text. This text can be logged as an aid to resolving issues. exchange: shortstr Specifies the name of the exchange that the message was originally published to. routing_key: shortstr Message routing key Specifies the routing key name specified when the message was published. """ self.returned_messages.put(basic_return_t( args.read_short(), args.read_shortstr(), args.read_shortstr(), args.read_shortstr(), msg, )) ############# # # Tx # # # work with standard transactions # # Standard transactions provide so-called "1.5 phase commit". We # can ensure that work is never lost, but there is a chance of # confirmations being lost, so that messages may be resent. # Applications that use standard transactions must be able to # detect and ignore duplicate messages. # # GRAMMAR:: # # tx = C:SELECT S:SELECT-OK # / C:COMMIT S:COMMIT-OK # / C:ROLLBACK S:ROLLBACK-OK # # RULE: # # An client using standard transactions SHOULD be able to # track all messages received within a reasonable period, and # thus detect and reject duplicates of the same message. It # SHOULD NOT pass these to the application layer. # # def tx_commit(self): """Commit the current transaction This method commits all messages published and acknowledged in the current transaction. A new transaction starts immediately after a commit. """ self._send_method((90, 20)) return self.wait(allowed_methods=[ (90, 21), # Channel.tx_commit_ok ]) def _tx_commit_ok(self, args): """Confirm a successful commit This method confirms to the client that the commit succeeded. Note that if a commit fails, the server raises a channel exception. """ pass def tx_rollback(self): """Abandon the current transaction This method abandons all messages published and acknowledged in the current transaction. A new transaction starts immediately after a rollback. """ self._send_method((90, 30)) return self.wait(allowed_methods=[ (90, 31), # Channel.tx_rollback_ok ]) def _tx_rollback_ok(self, args): """Confirm a successful rollback This method confirms to the client that the rollback succeeded. Note that if an rollback fails, the server raises a channel exception. """ pass def tx_select(self): """Select standard transaction mode This method sets the channel to use standard transactions. The client must use this method at least once on a channel before using the Commit or Rollback methods. """ self._send_method((90, 10)) return self.wait(allowed_methods=[ (90, 11), # Channel.tx_select_ok ]) def _tx_select_ok(self, args): """Confirm transaction mode This method confirms to the client that the channel was successfully set to use standard transactions. """ pass def confirm_select(self, nowait=False): """Enables publisher confirms for this channel (an RabbitMQ extension). Can now be used if the channel is in transactional mode. :param nowait: If set, the server will not respond to the method. The client should not wait for a reply method. If the server could not complete the method it will raise a channel or connection exception. """ args = AMQPWriter() args.write_bit(nowait) self._send_method((85, 10), args) if not nowait: self.wait(allowed_methods=[ (85, 11), # Confirm.select_ok ]) def _confirm_select_ok(self, args): """With this method the broker confirms to the client that the channel is now using publisher confirms.""" pass def _basic_ack_recv(self, args): delivery_tag = args.read_longlong() multiple = args.read_bit() self._apply_callbacks('basic_ack', delivery_tag, multiple) def _apply_callbacks(self, event, *args): return [callback(*args) for callback in self.events[event]] def _basic_nack(self, args): delivery_tag = args.read_longlong() multiple = args.read_bit() requeue = args.read_bit() if not self._apply_callbacks( 'basic_nack', delivery_tag, multiple, requeue): raise NotConfirmed(delivery_tag, (60, 120), 'basic.nack') _METHOD_MAP = { (20, 11): _open_ok, (20, 20): _flow, (20, 21): _flow_ok, (20, 40): _close, (20, 41): _close_ok, (40, 11): _exchange_declare_ok, (40, 21): _exchange_delete_ok, (40, 31): _exchange_bind_ok, (40, 51): _exchange_unbind_ok, (50, 11): _queue_declare_ok, (50, 21): _queue_bind_ok, (50, 31): _queue_purge_ok, (50, 41): _queue_delete_ok, (50, 51): _queue_unbind_ok, (60, 11): _basic_qos_ok, (60, 21): _basic_consume_ok, (60, 30): _basic_cancel_notify, (60, 31): _basic_cancel_ok, (60, 50): _basic_return, (60, 60): _basic_deliver, (60, 71): _basic_get_ok, (60, 72): _basic_get_empty, (60, 80): _basic_ack_recv, (60, 120): _basic_nack, (60, 111): _basic_recover_ok, (85, 11): _confirm_select_ok, (90, 11): _tx_select_ok, (90, 21): _tx_commit_ok, (90, 31): _tx_rollback_ok, } _IMMEDIATE_METHODS = [ (60, 50), # basic_return ] amqp-1.4.9/amqp/connection.py0000644000076500000000000010267312644026054016560 0ustar asksolwheel00000000000000"""AMQP 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 from __future__ import absolute_import import logging import socket from array import array try: from ssl import SSLError except ImportError: class SSLError(Exception): # noqa pass from . import __version__ from .abstract_channel import AbstractChannel from .channel import Channel from .exceptions import ( AMQPNotImplementedError, ChannelError, ResourceError, ConnectionForced, ConnectionError, error_for_code, RecoverableConnectionError, RecoverableChannelError, ) from .five import items, range, values, monotonic from .method_framing import MethodReader, MethodWriter from .serialization import AMQPWriter from .transport import create_transport HAS_MSG_PEEK = hasattr(socket, 'MSG_PEEK') START_DEBUG_FMT = """ Start from server, version: %d.%d, properties: %s, mechanisms: %s, locales: %s """.strip() __all__ = ['Connection'] # # Client property info that gets sent to the server on connection startup # LIBRARY_PROPERTIES = { 'product': 'py-amqp', 'product_version': __version__, 'capabilities': {}, } AMQP_LOGGER = logging.getLogger('amqp') class Connection(AbstractChannel): """The connection class provides methods for a client to establish a network connection to a server, and for both peers to operate the connection thereafter. GRAMMAR:: connection = open-connection *use-connection close-connection open-connection = C:protocol-header S:START C:START-OK *challenge S:TUNE C:TUNE-OK C:OPEN S:OPEN-OK challenge = S:SECURE C:SECURE-OK use-connection = *channel close-connection = C:CLOSE S:CLOSE-OK / S:CLOSE C:CLOSE-OK """ Channel = Channel #: Final heartbeat interval value (in float seconds) after negotiation heartbeat = None #: Original heartbeat interval value proposed by client. client_heartbeat = None #: Original heartbeat interval proposed by server. server_heartbeat = None #: Time of last heartbeat sent (in monotonic time, if available). last_heartbeat_sent = 0 #: Time of last heartbeat received (in monotonic time, if available). last_heartbeat_received = 0 #: Number of bytes sent to socket at the last heartbeat check. prev_sent = None #: Number of bytes received from socket at the last heartbeat check. prev_recv = None def __init__(self, host='localhost', userid='guest', password='guest', login_method='AMQPLAIN', login_response=None, virtual_host='/', locale='en_US', client_properties=None, ssl=False, connect_timeout=None, channel_max=None, frame_max=None, heartbeat=0, on_blocked=None, on_unblocked=None, confirm_publish=False, **kwargs): """Create a connection to the specified host, which should be a 'host[:port]', such as 'localhost', or '1.2.3.4:5672' (defaults to 'localhost', if a port is not specified then 5672 is used) If login_response is not specified, one is built up for you from userid and password if they are present. The 'ssl' parameter may be simply True/False, or for Python >= 2.6 a dictionary of options to pass to ssl.wrap_socket() such as requiring certain certificates. """ channel_max = channel_max or 65535 frame_max = frame_max or 131072 if (login_response is None) \ and (userid is not None) \ and (password is not None): login_response = AMQPWriter() login_response.write_table({'LOGIN': userid, 'PASSWORD': password}) # Skip the length at the beginning login_response = login_response.getvalue()[4:] d = dict(LIBRARY_PROPERTIES, **client_properties or {}) self._method_override = {(60, 50): self._dispatch_basic_return} self.channels = {} # The connection object itself is treated as channel 0 super(Connection, self).__init__(self, 0) self.transport = None # Properties set in the Tune method self.channel_max = channel_max self.frame_max = frame_max self.client_heartbeat = heartbeat self.confirm_publish = confirm_publish # Callbacks self.on_blocked = on_blocked self.on_unblocked = on_unblocked self._avail_channel_ids = array('H', range(self.channel_max, 0, -1)) # Properties set in the Start method self.version_major = 0 self.version_minor = 0 self.server_properties = {} self.mechanisms = [] self.locales = [] # Let the transport.py module setup the actual # socket connection to the broker. # self.transport = self.Transport(host, connect_timeout, ssl) self.method_reader = MethodReader(self.transport) self.method_writer = MethodWriter(self.transport, self.frame_max) self.wait(allowed_methods=[ (10, 10), # start ]) self._x_start_ok(d, login_method, login_response, locale) self._wait_tune_ok = True while self._wait_tune_ok: self.wait(allowed_methods=[ (10, 20), # secure (10, 30), # tune ]) return self._x_open(virtual_host) def Transport(self, host, connect_timeout, ssl=False): return create_transport(host, connect_timeout, ssl) @property def connected(self): return self.transport and self.transport.connected def _do_close(self): try: self.transport.close() temp_list = [x for x in values(self.channels) if x is not self] for ch in temp_list: ch._do_close() except socket.error: pass # connection already closed on the other end finally: self.transport = self.connection = self.channels = None def _get_free_channel_id(self): try: return self._avail_channel_ids.pop() except IndexError: raise ResourceError( 'No free channel ids, current={0}, channel_max={1}'.format( len(self.channels), self.channel_max), (20, 10)) def _claim_channel_id(self, channel_id): try: return self._avail_channel_ids.remove(channel_id) except ValueError: raise ConnectionError( 'Channel %r already open' % (channel_id, )) def _wait_method(self, channel_id, allowed_methods, timeout=None): """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 # read_timeout = self.read_timeout wait = self.wait while 1: channel, method_sig, args, content = read_timeout(timeout) if channel == channel_id and ( allowed_methods is None or method_sig in allowed_methods or method_sig == (20, 40)): return method_sig, args, content # # Certain methods like basic_return should be dispatched # immediately rather than being queued, even if they're not # one of the 'allowed_methods' we're looking for. # if channel and method_sig in self.Channel._IMMEDIATE_METHODS: self.channels[channel].dispatch_method( method_sig, args, content, ) continue # # Not the channel and/or method we were looking for. Queue # this method for later # self.channels[channel].method_queue.append( (method_sig, args, content), ) # # If we just queued up a method for channel 0 (the Connection # itself) it's probably a close method in reaction to some # error, so deal with it right away. # if not channel: wait() def channel(self, channel_id=None): """Fetch a Channel object identified by the numeric channel_id, or create that object if it doesn't already exist.""" try: return self.channels[channel_id] except KeyError: return self.Channel(self, channel_id) def is_alive(self): if HAS_MSG_PEEK: sock = self.sock prev = sock.gettimeout() sock.settimeout(0.0001) try: sock.recv(1, socket.MSG_PEEK) except socket.timeout: pass except socket.error: return False finally: sock.settimeout(prev) return True def drain_events(self, timeout=None): """Wait for an event on a channel.""" chanmap = self.channels chanid, method_sig, args, content = self._wait_multiple( chanmap, None, timeout=timeout, ) channel = chanmap[chanid] if (content and channel.auto_decode and hasattr(content, 'content_encoding')): try: content.body = content.body.decode(content.content_encoding) except Exception: pass amqp_method = (self._method_override.get(method_sig) or channel._METHOD_MAP.get(method_sig, None)) if amqp_method is None: raise AMQPNotImplementedError( 'Unknown AMQP method {0!r}'.format(method_sig)) if content is None: return amqp_method(channel, args) else: return amqp_method(channel, args, content) def read_timeout(self, timeout=None): if timeout is None: return self.method_reader.read_method() sock = self.sock prev = sock.gettimeout() if prev != timeout: sock.settimeout(timeout) try: try: return self.method_reader.read_method() except SSLError as exc: # http://bugs.python.org/issue10272 if 'timed out' in str(exc): raise socket.timeout() # Non-blocking SSL sockets can throw SSLError if 'The operation did not complete' in str(exc): raise socket.timeout() raise finally: if prev != timeout: sock.settimeout(prev) def _wait_multiple(self, channels, allowed_methods, timeout=None): for channel_id, channel in items(channels): method_queue = channel.method_queue for queued_method in method_queue: method_sig = queued_method[0] if (allowed_methods is None or method_sig in allowed_methods or method_sig == (20, 40)): method_queue.remove(queued_method) method_sig, args, content = queued_method return channel_id, method_sig, args, content # Nothing queued, need to wait for a method from the peer read_timeout = self.read_timeout wait = self.wait while 1: channel, method_sig, args, content = read_timeout(timeout) if channel in channels and ( allowed_methods is None or method_sig in allowed_methods or method_sig == (20, 40)): return channel, method_sig, args, content # Not the channel and/or method we were looking for. Queue # this method for later channels[channel].method_queue.append((method_sig, args, content)) # # If we just queued up a method for channel 0 (the Connection # itself) it's probably a close method in reaction to some # error, so deal with it right away. # if channel == 0: wait() def _dispatch_basic_return(self, channel, args, msg): reply_code = args.read_short() reply_text = args.read_shortstr() exchange = args.read_shortstr() routing_key = args.read_shortstr() exc = error_for_code(reply_code, reply_text, (50, 60), ChannelError) handlers = channel.events.get('basic_return') if not handlers: raise exc for callback in handlers: callback(exc, exchange, routing_key, msg) def close(self, reply_code=0, reply_text='', method_sig=(0, 0)): """Request a connection close This method indicates that the sender wants to close the connection. This may be due to internal conditions (e.g. a forced shut-down) or due to an error handling a specific method, i.e. an exception. When a close is due to an exception, the sender provides the class and method id of the method which caused the exception. RULE: After sending this method any received method except the Close-OK method MUST be discarded. RULE: The peer sending this method MAY use a counter or timeout to detect failure of the other peer to respond correctly with the Close-OK method. RULE: When a server receives the Close method from a client it MUST delete all server-side resources associated with the client's context. A client CANNOT reconnect to a context after sending or receiving a Close method. PARAMETERS: reply_code: short The reply code. The AMQ reply codes are defined in AMQ RFC 011. reply_text: shortstr The localised reply text. This text can be logged as an aid to resolving issues. class_id: short failing method class When the close is provoked by a method exception, this is the class of the method. method_id: short failing method ID When the close is provoked by a method exception, this is the ID of the method. """ if self.transport is None: # already closed return args = AMQPWriter() args.write_short(reply_code) args.write_shortstr(reply_text) args.write_short(method_sig[0]) # class_id args.write_short(method_sig[1]) # method_id self._send_method((10, 50), args) return self.wait(allowed_methods=[ (10, 50), # Connection.close (10, 51), # Connection.close_ok ]) def _close(self, args): """Request a connection close This method indicates that the sender wants to close the connection. This may be due to internal conditions (e.g. a forced shut-down) or due to an error handling a specific method, i.e. an exception. When a close is due to an exception, the sender provides the class and method id of the method which caused the exception. RULE: After sending this method any received method except the Close-OK method MUST be discarded. RULE: The peer sending this method MAY use a counter or timeout to detect failure of the other peer to respond correctly with the Close-OK method. RULE: When a server receives the Close method from a client it MUST delete all server-side resources associated with the client's context. A client CANNOT reconnect to a context after sending or receiving a Close method. PARAMETERS: reply_code: short The reply code. The AMQ reply codes are defined in AMQ RFC 011. reply_text: shortstr The localised reply text. This text can be logged as an aid to resolving issues. class_id: short failing method class When the close is provoked by a method exception, this is the class of the method. method_id: short failing method ID When the close is provoked by a method exception, this is the ID of the method. """ reply_code = args.read_short() reply_text = args.read_shortstr() class_id = args.read_short() method_id = args.read_short() self._x_close_ok() raise error_for_code(reply_code, reply_text, (class_id, method_id), ConnectionError) def _blocked(self, args): """RabbitMQ Extension.""" reason = args.read_shortstr() if self.on_blocked: return self.on_blocked(reason) def _unblocked(self, *args): if self.on_unblocked: return self.on_unblocked() def _x_close_ok(self): """Confirm a connection close This method confirms a Connection.Close method and tells the recipient that it is safe to release resources for the connection and close the socket. RULE: A peer that detects a socket closure without having received a Close-Ok handshake method SHOULD log the error. """ self._send_method((10, 51)) self._do_close() def _close_ok(self, args): """Confirm a connection close This method confirms a Connection.Close method and tells the recipient that it is safe to release resources for the connection and close the socket. RULE: A peer that detects a socket closure without having received a Close-Ok handshake method SHOULD log the error. """ self._do_close() def _x_open(self, virtual_host, capabilities=''): """Open connection to virtual host This method opens a connection to a virtual host, which is a collection of resources, and acts to separate multiple application domains within a server. RULE: The client MUST open the context before doing any work on the connection. PARAMETERS: virtual_host: shortstr virtual host name The name of the virtual host to work with. RULE: If the server supports multiple virtual hosts, it MUST enforce a full separation of exchanges, queues, and all associated entities per virtual host. An application, connected to a specific virtual host, MUST NOT be able to access resources of another virtual host. RULE: The server SHOULD verify that the client has permission to access the specified virtual host. RULE: The server MAY configure arbitrary limits per virtual host, such as the number of each type of entity that may be used, per connection and/or in total. capabilities: shortstr required capabilities The client may specify a number of capability names, delimited by spaces. The server can use this string to how to process the client's connection request. """ args = AMQPWriter() args.write_shortstr(virtual_host) args.write_shortstr(capabilities) args.write_bit(False) self._send_method((10, 40), args) return self.wait(allowed_methods=[ (10, 41), # Connection.open_ok ]) def _open_ok(self, args): """Signal that the connection is ready This method signals to the client that the connection is ready for use. PARAMETERS: known_hosts: shortstr (deprecated) """ AMQP_LOGGER.debug('Open OK!') def _secure(self, args): """Security mechanism challenge The SASL protocol works by exchanging challenges and responses until both peers have received sufficient information to authenticate each other. This method challenges the client to provide more information. PARAMETERS: challenge: longstr security challenge data Challenge information, a block of opaque binary data passed to the security mechanism. """ challenge = args.read_longstr() # noqa def _x_secure_ok(self, response): """Security mechanism response This method attempts to authenticate, passing a block of SASL data for the security mechanism at the server side. PARAMETERS: response: longstr security response data A block of opaque data passed to the security mechanism. The contents of this data are defined by the SASL security mechanism. """ args = AMQPWriter() args.write_longstr(response) self._send_method((10, 21), args) def _start(self, args): """Start connection negotiation This method starts the connection negotiation process by telling the client the protocol version that the server proposes, along with a list of security mechanisms which the client can use for authentication. RULE: If the client cannot handle the protocol version suggested by the server it MUST close the socket connection. RULE: The server MUST provide a protocol version that is lower than or equal to that requested by the client in the protocol header. If the server cannot support the specified protocol it MUST NOT send this method, but MUST close the socket connection. PARAMETERS: version_major: octet protocol major version The protocol major version that the server agrees to use, which cannot be higher than the client's major version. version_minor: octet protocol major version The protocol minor version that the server agrees to use, which cannot be higher than the client's minor version. server_properties: table server properties mechanisms: longstr available security mechanisms A list of the security mechanisms that the server supports, delimited by spaces. Currently ASL supports these mechanisms: PLAIN. locales: longstr available message locales A list of the message locales that the server supports, delimited by spaces. The locale defines the language in which the server will send reply texts. RULE: All servers MUST support at least the en_US locale. """ self.version_major = args.read_octet() self.version_minor = args.read_octet() self.server_properties = args.read_table() self.mechanisms = args.read_longstr().split(' ') self.locales = args.read_longstr().split(' ') AMQP_LOGGER.debug( START_DEBUG_FMT, self.version_major, self.version_minor, self.server_properties, self.mechanisms, self.locales, ) def _x_start_ok(self, client_properties, mechanism, response, locale): """Select security mechanism and locale This method selects a SASL security mechanism. ASL uses SASL (RFC2222) to negotiate authentication and encryption. PARAMETERS: client_properties: table client properties mechanism: shortstr selected security mechanism A single security mechanisms selected by the client, which must be one of those specified by the server. RULE: The client SHOULD authenticate using the highest- level security profile it can handle from the list provided by the server. RULE: The mechanism field MUST contain one of the security mechanisms proposed by the server in the Start method. If it doesn't, the server MUST close the socket. response: longstr security response data A block of opaque data passed to the security mechanism. The contents of this data are defined by the SASL security mechanism. For the PLAIN security mechanism this is defined as a field table holding two fields, LOGIN and PASSWORD. locale: shortstr selected message locale A single message local selected by the client, which must be one of those specified by the server. """ if self.server_capabilities.get('consumer_cancel_notify'): if 'capabilities' not in client_properties: client_properties['capabilities'] = {} client_properties['capabilities']['consumer_cancel_notify'] = True if self.server_capabilities.get('connection.blocked'): if 'capabilities' not in client_properties: client_properties['capabilities'] = {} client_properties['capabilities']['connection.blocked'] = True args = AMQPWriter() args.write_table(client_properties) args.write_shortstr(mechanism) args.write_longstr(response) args.write_shortstr(locale) self._send_method((10, 11), args) def _tune(self, args): """Propose connection tuning parameters This method proposes a set of connection configuration values to the client. The client can accept and/or adjust these. PARAMETERS: channel_max: short proposed maximum channels The maximum total number of channels that the server allows per connection. Zero means that the server does not impose a fixed limit, but the number of allowed channels may be limited by available server resources. frame_max: long proposed maximum frame size The largest frame size that the server proposes for the connection. The client can negotiate a lower value. Zero means that the server does not impose any specific limit but may reject very large frames if it cannot allocate resources for them. RULE: Until the frame-max has been negotiated, both peers MUST accept frames of up to 4096 octets large. The minimum non-zero value for the frame- max field is 4096. heartbeat: short desired heartbeat delay The delay, in seconds, of the connection heartbeat that the server wants. Zero means the server does not want a heartbeat. """ client_heartbeat = self.client_heartbeat or 0 self.channel_max = args.read_short() or self.channel_max self.frame_max = args.read_long() or self.frame_max self.method_writer.frame_max = self.frame_max self.server_heartbeat = args.read_short() or 0 # negotiate the heartbeat interval to the smaller of the # specified values if self.server_heartbeat == 0 or client_heartbeat == 0: self.heartbeat = max(self.server_heartbeat, client_heartbeat) else: self.heartbeat = min(self.server_heartbeat, client_heartbeat) # Ignore server heartbeat if client_heartbeat is disabled if not self.client_heartbeat: self.heartbeat = 0 self._x_tune_ok(self.channel_max, self.frame_max, self.heartbeat) def send_heartbeat(self): self.transport.write_frame(8, 0, bytes()) def heartbeat_tick(self, rate=2): """Send heartbeat packets, if necessary, and fail if none have been received recently. This should be called frequently, on the order of once per second. :keyword rate: Ignored """ if not self.heartbeat: return # treat actual data exchange in either direction as a heartbeat sent_now = self.method_writer.bytes_sent recv_now = self.method_reader.bytes_recv if self.prev_sent is None or self.prev_sent != sent_now: self.last_heartbeat_sent = monotonic() if self.prev_recv is None or self.prev_recv != recv_now: self.last_heartbeat_received = monotonic() self.prev_sent, self.prev_recv = sent_now, recv_now # send a heartbeat if it's time to do so if monotonic() > self.last_heartbeat_sent + self.heartbeat: self.send_heartbeat() self.last_heartbeat_sent = monotonic() # if we've missed two intervals' heartbeats, fail; this gives the # server enough time to send heartbeats a little late if (self.last_heartbeat_received and self.last_heartbeat_received + 2 * self.heartbeat < monotonic()): raise ConnectionForced('Too many heartbeats missed') def _x_tune_ok(self, channel_max, frame_max, heartbeat): """Negotiate connection tuning parameters This method sends the client's connection tuning parameters to the server. Certain fields are negotiated, others provide capability information. PARAMETERS: channel_max: short negotiated maximum channels The maximum total number of channels that the client will use per connection. May not be higher than the value specified by the server. RULE: The server MAY ignore the channel-max value or MAY use it for tuning its resource allocation. frame_max: long negotiated maximum frame size The largest frame size that the client and server will use for the connection. Zero means that the client does not impose any specific limit but may reject very large frames if it cannot allocate resources for them. Note that the frame-max limit applies principally to content frames, where large contents can be broken into frames of arbitrary size. RULE: Until the frame-max has been negotiated, both peers must accept frames of up to 4096 octets large. The minimum non-zero value for the frame- max field is 4096. heartbeat: short desired heartbeat delay The delay, in seconds, of the connection heartbeat that the client wants. Zero means the client does not want a heartbeat. """ args = AMQPWriter() args.write_short(channel_max) args.write_long(frame_max) args.write_short(heartbeat or 0) self._send_method((10, 31), args) self._wait_tune_ok = False @property def sock(self): return self.transport.sock @property def server_capabilities(self): return self.server_properties.get('capabilities') or {} _METHOD_MAP = { (10, 10): _start, (10, 20): _secure, (10, 30): _tune, (10, 41): _open_ok, (10, 50): _close, (10, 51): _close_ok, (10, 60): _blocked, (10, 61): _unblocked, } _IMMEDIATE_METHODS = [] connection_errors = ( ConnectionError, socket.error, IOError, OSError, ) channel_errors = (ChannelError, ) recoverable_connection_errors = ( RecoverableConnectionError, socket.error, IOError, OSError, ) recoverable_channel_errors = ( RecoverableChannelError, ) amqp-1.4.9/amqp/exceptions.py0000644000076500000000000001530412644026054016574 0ustar asksolwheel00000000000000"""Exceptions used by 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 __future__ import absolute_import from struct import pack, unpack __all__ = [ 'AMQPError', 'ConnectionError', 'ChannelError', 'RecoverableConnectionError', 'IrrecoverableConnectionError', 'RecoverableChannelError', 'IrrecoverableChannelError', 'ConsumerCancelled', 'ContentTooLarge', 'NoConsumers', 'ConnectionForced', 'InvalidPath', 'AccessRefused', 'NotFound', 'ResourceLocked', 'PreconditionFailed', 'FrameError', 'FrameSyntaxError', 'InvalidCommand', 'ChannelNotOpen', 'UnexpectedFrame', 'ResourceError', 'NotConfirmed', 'NotAllowed', 'AMQPNotImplementedError', 'InternalError', ] class AMQPError(Exception): code = 0 def __init__(self, reply_text=None, method_sig=None, method_name=None, reply_code=None): self.message = reply_text self.reply_code = reply_code or self.code self.reply_text = reply_text self.method_sig = method_sig self.method_name = method_name or '' if method_sig and not self.method_name: self.method_name = METHOD_NAME_MAP.get(method_sig, '') Exception.__init__(self, reply_code, reply_text, method_sig, self.method_name) def __str__(self): if self.method: return '{0.method}: ({0.reply_code}) {0.reply_text}'.format(self) return self.reply_text or '' @property def method(self): return self.method_name or self.method_sig class ConnectionError(AMQPError): pass class ChannelError(AMQPError): pass class RecoverableChannelError(ChannelError): pass class IrrecoverableChannelError(ChannelError): pass class RecoverableConnectionError(ConnectionError): pass class IrrecoverableConnectionError(ConnectionError): pass class Blocked(RecoverableConnectionError): pass class ConsumerCancelled(RecoverableConnectionError): pass class ContentTooLarge(RecoverableChannelError): code = 311 class NoConsumers(RecoverableChannelError): code = 313 class ConnectionForced(RecoverableConnectionError): code = 320 class InvalidPath(IrrecoverableConnectionError): code = 402 class AccessRefused(IrrecoverableChannelError): code = 403 class NotFound(IrrecoverableChannelError): code = 404 class NotConfirmed(RecoverableConnectionError): pass class ResourceLocked(RecoverableChannelError): code = 405 class PreconditionFailed(IrrecoverableChannelError): code = 406 class FrameError(IrrecoverableConnectionError): code = 501 class FrameSyntaxError(IrrecoverableConnectionError): code = 502 class InvalidCommand(IrrecoverableConnectionError): code = 503 class ChannelNotOpen(IrrecoverableConnectionError): code = 504 class UnexpectedFrame(IrrecoverableConnectionError): code = 505 class ResourceError(RecoverableConnectionError): code = 506 class NotAllowed(IrrecoverableConnectionError): code = 530 class AMQPNotImplementedError(IrrecoverableConnectionError): code = 540 class InternalError(IrrecoverableConnectionError): code = 541 ERROR_MAP = { 311: ContentTooLarge, 313: NoConsumers, 320: ConnectionForced, 402: InvalidPath, 403: AccessRefused, 404: NotFound, 405: ResourceLocked, 406: PreconditionFailed, 501: FrameError, 502: FrameSyntaxError, 503: InvalidCommand, 504: ChannelNotOpen, 505: UnexpectedFrame, 506: ResourceError, 530: NotAllowed, 540: AMQPNotImplementedError, 541: InternalError, } def error_for_code(code, text, method, default): try: return ERROR_MAP[code](text, method, reply_code=code) except KeyError: return default(text, method, reply_code=code) def raise_for_code(code, text, method, default): raise error_for_code(code, text, method, default) METHOD_NAME_MAP = { (10, 10): 'Connection.start', (10, 11): 'Connection.start_ok', (10, 20): 'Connection.secure', (10, 21): 'Connection.secure_ok', (10, 30): 'Connection.tune', (10, 31): 'Connection.tune_ok', (10, 40): 'Connection.open', (10, 41): 'Connection.open_ok', (10, 50): 'Connection.close', (10, 51): 'Connection.close_ok', (20, 10): 'Channel.open', (20, 11): 'Channel.open_ok', (20, 20): 'Channel.flow', (20, 21): 'Channel.flow_ok', (20, 40): 'Channel.close', (20, 41): 'Channel.close_ok', (30, 10): 'Access.request', (30, 11): 'Access.request_ok', (40, 10): 'Exchange.declare', (40, 11): 'Exchange.declare_ok', (40, 20): 'Exchange.delete', (40, 21): 'Exchange.delete_ok', (40, 30): 'Exchange.bind', (40, 31): 'Exchange.bind_ok', (40, 40): 'Exchange.unbind', (40, 41): 'Exchange.unbind_ok', (50, 10): 'Queue.declare', (50, 11): 'Queue.declare_ok', (50, 20): 'Queue.bind', (50, 21): 'Queue.bind_ok', (50, 30): 'Queue.purge', (50, 31): 'Queue.purge_ok', (50, 40): 'Queue.delete', (50, 41): 'Queue.delete_ok', (50, 50): 'Queue.unbind', (50, 51): 'Queue.unbind_ok', (60, 10): 'Basic.qos', (60, 11): 'Basic.qos_ok', (60, 20): 'Basic.consume', (60, 21): 'Basic.consume_ok', (60, 30): 'Basic.cancel', (60, 31): 'Basic.cancel_ok', (60, 40): 'Basic.publish', (60, 50): 'Basic.return', (60, 60): 'Basic.deliver', (60, 70): 'Basic.get', (60, 71): 'Basic.get_ok', (60, 72): 'Basic.get_empty', (60, 80): 'Basic.ack', (60, 90): 'Basic.reject', (60, 100): 'Basic.recover_async', (60, 110): 'Basic.recover', (60, 111): 'Basic.recover_ok', (60, 120): 'Basic.nack', (90, 10): 'Tx.select', (90, 11): 'Tx.select_ok', (90, 20): 'Tx.commit', (90, 21): 'Tx.commit_ok', (90, 30): 'Tx.rollback', (90, 31): 'Tx.rollback_ok', (85, 10): 'Confirm.select', (85, 11): 'Confirm.select_ok', } for _method_id, _method_name in list(METHOD_NAME_MAP.items()): METHOD_NAME_MAP[unpack('>I', pack('>HH', *_method_id))[0]] = _method_name amqp-1.4.9/amqp/five.py0000644000076500000000000001252112644026054015342 0ustar asksolwheel00000000000000# -*- coding: utf-8 -*- """ celery.five ~~~~~~~~~~~ Compatibility implementations of features only available in newer Python versions. """ from __future__ import absolute_import # ############# py3k ######################################################### import sys PY3 = sys.version_info[0] == 3 try: reload = reload # noqa except NameError: # pragma: no cover from imp import reload # noqa try: from UserList import UserList # noqa except ImportError: # pragma: no cover from collections import UserList # noqa try: from UserDict import UserDict # noqa except ImportError: # pragma: no cover from collections import UserDict # noqa if PY3: import builtins from queue import Queue, Empty from itertools import zip_longest from io import StringIO, BytesIO map = map string = str string_t = str long_t = int text_t = str range = range int_types = (int, ) open_fqdn = 'builtins.open' def items(d): return d.items() def keys(d): return d.keys() def values(d): return d.values() def nextfun(it): return it.__next__ exec_ = getattr(builtins, 'exec') def reraise(tp, value, tb=None): if value.__traceback__ is not tb: raise value.with_traceback(tb) raise value class WhateverIO(StringIO): def write(self, data): if isinstance(data, bytes): data = data.encode() StringIO.write(self, data) else: import __builtin__ as builtins # noqa from Queue import Queue, Empty # noqa from itertools import imap as map, izip_longest as zip_longest # noqa from StringIO import StringIO # noqa string = unicode # noqa string_t = basestring # noqa text_t = unicode long_t = long # noqa range = xrange int_types = (int, long) open_fqdn = '__builtin__.open' def items(d): # noqa return d.iteritems() def keys(d): # noqa return d.iterkeys() def values(d): # noqa return d.itervalues() def nextfun(it): # noqa return it.next def exec_(code, globs=None, locs=None): """Execute code in a namespace.""" if globs is None: frame = sys._getframe(1) globs = frame.f_globals if locs is None: locs = frame.f_locals del frame elif locs is None: locs = globs exec("""exec code in globs, locs""") exec_("""def reraise(tp, value, tb=None): raise tp, value, tb""") BytesIO = WhateverIO = StringIO # noqa def with_metaclass(Type, skip_attrs=set(['__dict__', '__weakref__'])): """Class decorator to set metaclass. Works with both Python 3 and Python 3 and it does not add an extra class in the lookup order like ``six.with_metaclass`` does (that is -- it copies the original class instead of using inheritance). """ def _clone_with_metaclass(Class): attrs = dict((key, value) for key, value in items(vars(Class)) if key not in skip_attrs) return Type(Class.__name__, Class.__bases__, attrs) return _clone_with_metaclass # ############# time.monotonic ################################################ if sys.version_info < (3, 3): import platform SYSTEM = platform.system() try: import ctypes except ImportError: # pragma: no cover ctypes = None # noqa if SYSTEM == 'Darwin' and ctypes is not None: from ctypes.util import find_library libSystem = ctypes.CDLL(find_library('libSystem.dylib')) CoreServices = ctypes.CDLL(find_library('CoreServices'), use_errno=True) mach_absolute_time = libSystem.mach_absolute_time mach_absolute_time.restype = ctypes.c_uint64 absolute_to_nanoseconds = CoreServices.AbsoluteToNanoseconds absolute_to_nanoseconds.restype = ctypes.c_uint64 absolute_to_nanoseconds.argtypes = [ctypes.c_uint64] def _monotonic(): return absolute_to_nanoseconds(mach_absolute_time()) * 1e-9 elif SYSTEM == 'Linux' and ctypes is not None: # from stackoverflow: # questions/1205722/how-do-i-get-monotonic-time-durations-in-python import os CLOCK_MONOTONIC = 1 # see class timespec(ctypes.Structure): _fields_ = [ ('tv_sec', ctypes.c_long), ('tv_nsec', ctypes.c_long), ] librt = ctypes.CDLL('librt.so.1', use_errno=True) clock_gettime = librt.clock_gettime clock_gettime.argtypes = [ ctypes.c_int, ctypes.POINTER(timespec), ] def _monotonic(): # noqa t = timespec() if clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(t)) != 0: errno_ = ctypes.get_errno() raise OSError(errno_, os.strerror(errno_)) return t.tv_sec + t.tv_nsec * 1e-9 else: from time import time as _monotonic try: from time import monotonic except ImportError: monotonic = _monotonic # noqa amqp-1.4.9/amqp/method_framing.py0000644000076500000000000001756312644026054017407 0ustar asksolwheel00000000000000"""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 __future__ import absolute_import from collections import defaultdict, deque from struct import pack, unpack from .basic_message import Message from .exceptions import AMQPError, UnexpectedFrame from .five import range, string from .serialization import AMQPReader __all__ = ['MethodReader'] # # MethodReader needs to know which methods are supposed # to be followed by content headers and bodies. # _CONTENT_METHODS = [ (60, 50), # Basic.return (60, 60), # Basic.deliver (60, 71), # Basic.get_ok ] class _PartialMessage(object): """Helper class to build up a multi-frame method.""" def __init__(self, method_sig, args, channel): self.method_sig = method_sig self.args = args self.msg = Message() self.body_parts = [] self.body_received = 0 self.body_size = None self.complete = False def add_header(self, payload): class_id, weight, self.body_size = unpack('>HHQ', payload[:12]) self.msg._load_properties(payload[12:]) self.complete = (self.body_size == 0) def add_payload(self, payload): parts = self.body_parts self.body_received += len(payload) if self.body_received == self.body_size: if parts: parts.append(payload) self.msg.body = bytes().join(parts) else: self.msg.body = payload self.complete = True else: parts.append(payload) class MethodReader(object): """Helper class to receive frames from the broker, combine them if necessary with content-headers and content-bodies into complete methods. Normally a method is represented as a tuple containing (channel, method_sig, args, content). In the case of a framing error, an :exc:`ConnectionError` is placed in the queue. In the case of unexpected frames, a tuple made up of ``(channel, ChannelError)`` is placed in the queue. """ def __init__(self, source): self.source = source self.queue = deque() self.running = False self.partial_messages = {} self.heartbeats = 0 # For each channel, which type is expected next self.expected_types = defaultdict(lambda: 1) # not an actual byte count, just incremented whenever we receive self.bytes_recv = 0 self._quick_put = self.queue.append self._quick_get = self.queue.popleft def _next_method(self): """Read the next method from the source, once one complete method has been assembled it is placed in the internal queue.""" queue = self.queue put = self._quick_put read_frame = self.source.read_frame while not queue: try: frame_type, channel, payload = read_frame() except Exception as exc: # # Connection was closed? Framing Error? # put(exc) break self.bytes_recv += 1 if frame_type not in (self.expected_types[channel], 8): put(( channel, UnexpectedFrame( 'Received frame {0} while expecting type: {1}'.format( frame_type, self.expected_types[channel])))) elif frame_type == 1: self._process_method_frame(channel, payload) elif frame_type == 2: self._process_content_header(channel, payload) elif frame_type == 3: self._process_content_body(channel, payload) elif frame_type == 8: self._process_heartbeat(channel, payload) def _process_heartbeat(self, channel, payload): self.heartbeats += 1 def _process_method_frame(self, channel, payload): """Process Method frames""" method_sig = unpack('>HH', payload[:4]) args = AMQPReader(payload[4:]) if method_sig in _CONTENT_METHODS: # # Save what we've got so far and wait for the content-header # self.partial_messages[channel] = _PartialMessage( method_sig, args, channel, ) self.expected_types[channel] = 2 else: self._quick_put((channel, method_sig, args, None)) def _process_content_header(self, channel, payload): """Process Content Header frames""" partial = self.partial_messages[channel] partial.add_header(payload) if partial.complete: # # a bodyless message, we're done # self._quick_put((channel, partial.method_sig, partial.args, partial.msg)) self.partial_messages.pop(channel, None) self.expected_types[channel] = 1 else: # # wait for the content-body # self.expected_types[channel] = 3 def _process_content_body(self, channel, payload): """Process Content Body frames""" partial = self.partial_messages[channel] partial.add_payload(payload) if partial.complete: # # Stick the message in the queue and go back to # waiting for method frames # self._quick_put((channel, partial.method_sig, partial.args, partial.msg)) self.partial_messages.pop(channel, None) self.expected_types[channel] = 1 def read_method(self): """Read a method from the peer.""" self._next_method() m = self._quick_get() if isinstance(m, Exception): raise m if isinstance(m, tuple) and isinstance(m[1], AMQPError): raise m[1] return m class MethodWriter(object): """Convert AMQP methods into AMQP frames and send them out to the peer.""" def __init__(self, dest, frame_max): self.dest = dest self.frame_max = frame_max self.bytes_sent = 0 def write_method(self, channel, method_sig, args, content=None): write_frame = self.dest.write_frame payload = pack('>HH', method_sig[0], method_sig[1]) + args if content: # do this early, so we can raise an exception if there's a # problem with the content properties, before sending the # first frame body = content.body if isinstance(body, string): coding = content.properties.get('content_encoding', None) if coding is None: coding = content.properties['content_encoding'] = 'UTF-8' body = body.encode(coding) properties = content._serialize_properties() write_frame(1, channel, payload) if content: payload = pack('>HHQ', method_sig[0], 0, len(body)) + properties write_frame(2, channel, payload) chunk_size = self.frame_max - 8 for i in range(0, len(body), chunk_size): write_frame(3, channel, body[i:i + chunk_size]) self.bytes_sent += 1 amqp-1.4.9/amqp/protocol.py0000644000076500000000000000046612270020623016246 0ustar asksolwheel00000000000000from __future__ import absolute_import from collections import namedtuple queue_declare_ok_t = namedtuple( 'queue_declare_ok_t', ('queue', 'message_count', 'consumer_count'), ) basic_return_t = namedtuple( 'basic_return_t', ('reply_code', 'reply_text', 'exchange', 'routing_key', 'message'), ) amqp-1.4.9/amqp/serialization.py0000644000076500000000000003767312644026054017305 0ustar asksolwheel00000000000000""" 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 from __future__ import absolute_import import calendar import sys from datetime import datetime from decimal import Decimal from io import BytesIO from struct import pack, unpack from .exceptions import FrameSyntaxError from .five import int_types, long_t, string, string_t, items IS_PY3K = sys.version_info[0] >= 3 if IS_PY3K: def byte(n): return bytes([n]) else: byte = chr ILLEGAL_TABLE_TYPE_WITH_KEY = """\ Table type {0!r} for key {1!r} not handled by amqp. [value: {2!r}] """ ILLEGAL_TABLE_TYPE = """\ Table type {0!r} not handled by amqp. [value: {1!r}] """ class AMQPReader(object): """Read higher-level AMQP types from a bytestream.""" def __init__(self, source): """Source should be either a file-like object with a read() method, or a plain (non-unicode) string.""" if isinstance(source, bytes): self.input = BytesIO(source) elif hasattr(source, 'read'): self.input = source else: raise ValueError( 'AMQPReader needs a file-like object or plain string') self.bitcount = self.bits = 0 def close(self): self.input.close() def read(self, n): """Read n bytes.""" self.bitcount = self.bits = 0 return self.input.read(n) def read_bit(self): """Read a single boolean value.""" if not self.bitcount: self.bits = ord(self.input.read(1)) self.bitcount = 8 result = (self.bits & 1) == 1 self.bits >>= 1 self.bitcount -= 1 return result def read_octet(self): """Read one byte, return as an integer""" self.bitcount = self.bits = 0 return unpack('B', self.input.read(1))[0] def read_short(self): """Read an unsigned 16-bit integer""" self.bitcount = self.bits = 0 return unpack('>H', self.input.read(2))[0] def read_long(self): """Read an unsigned 32-bit integer""" self.bitcount = self.bits = 0 return unpack('>I', self.input.read(4))[0] def read_longlong(self): """Read an unsigned 64-bit integer""" self.bitcount = self.bits = 0 return unpack('>Q', self.input.read(8))[0] def read_float(self): """Read float value.""" self.bitcount = self.bits = 0 return unpack('>d', self.input.read(8))[0] def read_shortstr(self): """Read a short string that's stored in up to 255 bytes. The encoding isn't specified in the AMQP spec, so assume it's utf-8 """ self.bitcount = self.bits = 0 slen = unpack('B', self.input.read(1))[0] return self.input.read(slen).decode('utf-8') def read_longstr(self): """Read a string that's up to 2**32 bytes. The encoding isn't specified in the AMQP spec, so assume it's utf-8 """ self.bitcount = self.bits = 0 slen = unpack('>I', self.input.read(4))[0] return self.input.read(slen).decode('utf-8') def read_table(self): """Read an AMQP table, and return as a Python dictionary.""" self.bitcount = self.bits = 0 tlen = unpack('>I', self.input.read(4))[0] table_data = AMQPReader(self.input.read(tlen)) result = {} while table_data.input.tell() < tlen: name = table_data.read_shortstr() val = table_data.read_item() result[name] = val return result def read_item(self): ftype = ord(self.input.read(1)) # 'S': long string if ftype == 83: val = self.read_longstr() # 's': short string elif ftype == 115: val = self.read_shortstr() # 'b': short-short int elif ftype == 98: val, = unpack('>B', self.input.read(1)) # 'B': short-short unsigned int elif ftype == 66: val, = unpack('>b', self.input.read(1)) # 'U': short int elif ftype == 85: val, = unpack('>h', self.input.read(2)) # 'u': short unsigned int elif ftype == 117: val, = unpack('>H', self.input.read(2)) # 'I': long int elif ftype == 73: val, = unpack('>i', self.input.read(4)) # 'i': long unsigned int elif ftype == 105: # 'l' val, = unpack('>I', self.input.read(4)) # 'L': long long int elif ftype == 76: val, = unpack('>q', self.input.read(8)) # 'l': long long unsigned int elif ftype == 108: val, = unpack('>Q', self.input.read(8)) # 'f': float elif ftype == 102: val, = unpack('>f', self.input.read(4)) # 'd': double elif ftype == 100: val = self.read_float() # 'D': decimal elif ftype == 68: d = self.read_octet() n, = unpack('>i', self.input.read(4)) val = Decimal(n) / Decimal(10 ** d) # 'F': table elif ftype == 70: val = self.read_table() # recurse # 'A': array elif ftype == 65: val = self.read_array() # 't' (bool) elif ftype == 116: val = self.read_bit() # 'T': timestamp elif ftype == 84: val = self.read_timestamp() # 'V': void elif ftype == 86: val = None else: raise FrameSyntaxError( 'Unknown value in table: {0!r} ({1!r})'.format( ftype, type(ftype))) return val def read_array(self): array_length = unpack('>I', self.input.read(4))[0] array_data = AMQPReader(self.input.read(array_length)) result = [] while array_data.input.tell() < array_length: val = array_data.read_item() result.append(val) return result def read_timestamp(self): """Read and AMQP timestamp, which is a 64-bit integer representing seconds since the Unix epoch in 1-second resolution. Return as a Python datetime.datetime object, expressed as localtime. """ return datetime.utcfromtimestamp(self.read_longlong()) class AMQPWriter(object): """Convert higher-level AMQP types to bytestreams.""" def __init__(self, dest=None): """dest may be a file-type object (with a write() method). If None then a BytesIO is created, and the contents can be accessed with this class's getvalue() method.""" self.out = BytesIO() if dest is None else dest self.bits = [] self.bitcount = 0 def _flushbits(self): if self.bits: out = self.out for b in self.bits: out.write(pack('B', b)) self.bits = [] self.bitcount = 0 def close(self): """Pass through if possible to any file-like destinations.""" try: self.out.close() except AttributeError: pass def flush(self): """Pass through if possible to any file-like destinations.""" try: self.out.flush() except AttributeError: pass def getvalue(self): """Get what's been encoded so far if we're working with a BytesIO.""" self._flushbits() return self.out.getvalue() def write(self, s): """Write a plain Python string with no special encoding in Python 2.x, or bytes in Python 3.x""" self._flushbits() self.out.write(s) def write_bit(self, b): """Write a boolean value.""" b = 1 if b else 0 shift = self.bitcount % 8 if shift == 0: self.bits.append(0) self.bits[-1] |= (b << shift) self.bitcount += 1 def write_octet(self, n): """Write an integer as an unsigned 8-bit value.""" if n < 0 or n > 255: raise FrameSyntaxError( 'Octet {0!r} out of range 0..255'.format(n)) self._flushbits() self.out.write(pack('B', n)) def write_short(self, n): """Write an integer as an unsigned 16-bit value.""" if n < 0 or n > 65535: raise FrameSyntaxError( 'Octet {0!r} out of range 0..65535'.format(n)) self._flushbits() self.out.write(pack('>H', int(n))) def write_long(self, n): """Write an integer as an unsigned2 32-bit value.""" if n < 0 or n >= 4294967296: raise FrameSyntaxError( 'Octet {0!r} out of range 0..2**31-1'.format(n)) self._flushbits() self.out.write(pack('>I', n)) def write_longlong(self, n): """Write an integer as an unsigned 64-bit value.""" if n < 0 or n >= 18446744073709551616: raise FrameSyntaxError( 'Octet {0!r} out of range 0..2**64-1'.format(n)) self._flushbits() self.out.write(pack('>Q', n)) def write_shortstr(self, s): """Write a string up to 255 bytes long (after any encoding). If passed a unicode string, encode with UTF-8. """ self._flushbits() if isinstance(s, string): s = s.encode('utf-8') if len(s) > 255: raise FrameSyntaxError( 'Shortstring overflow ({0} > 255)'.format(len(s))) self.write_octet(len(s)) self.out.write(s) def write_longstr(self, s): """Write a string up to 2**32 bytes long after encoding. If passed a unicode string, encode as UTF-8. """ self._flushbits() if isinstance(s, string): s = s.encode('utf-8') self.write_long(len(s)) self.out.write(s) def write_table(self, d): """Write out a Python dictionary made of up string keys, and values that are strings, signed integers, Decimal, datetime.datetime, or sub-dictionaries following the same constraints.""" self._flushbits() table_data = AMQPWriter() for k, v in items(d): table_data.write_shortstr(k) table_data.write_item(v, k) table_data = table_data.getvalue() self.write_long(len(table_data)) self.out.write(table_data) def write_item(self, v, k=None): if isinstance(v, (string_t, bytes)): if isinstance(v, string): v = v.encode('utf-8') self.write(b'S') self.write_longstr(v) elif isinstance(v, bool): self.write(pack('>cB', b't', int(v))) elif isinstance(v, float): self.write(pack('>cd', b'd', v)) elif isinstance(v, int_types): self.write(pack('>ci', b'I', v)) elif isinstance(v, Decimal): self.write(b'D') sign, digits, exponent = v.as_tuple() v = 0 for d in digits: v = (v * 10) + d if sign: v = -v self.write_octet(-exponent) self.write(pack('>i', v)) elif isinstance(v, datetime): self.write(b'T') self.write_timestamp(v) elif isinstance(v, dict): self.write(b'F') self.write_table(v) elif isinstance(v, (list, tuple)): self.write(b'A') self.write_array(v) elif v is None: self.write(b'V') else: err = (ILLEGAL_TABLE_TYPE_WITH_KEY.format(type(v), k, v) if k else ILLEGAL_TABLE_TYPE.format(type(v), v)) raise FrameSyntaxError(err) def write_array(self, a): array_data = AMQPWriter() for v in a: array_data.write_item(v) array_data = array_data.getvalue() self.write_long(len(array_data)) self.out.write(array_data) def write_timestamp(self, v): """Write out a Python datetime.datetime object as a 64-bit integer representing seconds since the Unix epoch.""" self.out.write(pack('>Q', long_t(calendar.timegm(v.utctimetuple())))) class GenericContent(object): """Abstract base class for AMQP content. Subclasses should override the PROPERTIES attribute. """ PROPERTIES = [('dummy', 'shortstr')] def __init__(self, **props): """Save the properties appropriate to this AMQP content type in a 'properties' dictionary.""" d = {} for propname, _ in self.PROPERTIES: if propname in props: d[propname] = props[propname] # FIXME: should we ignore unknown properties? self.properties = d def __eq__(self, other): """Check if this object has the same properties as another content object.""" try: return self.properties == other.properties except AttributeError: return NotImplemented def __getattr__(self, name): """Look for additional properties in the 'properties' dictionary, and if present - the 'delivery_info' dictionary.""" if name == '__setstate__': # Allows pickling/unpickling to work raise AttributeError('__setstate__') if name in self.properties: return self.properties[name] if 'delivery_info' in self.__dict__ \ and name in self.delivery_info: return self.delivery_info[name] raise AttributeError(name) def _load_properties(self, raw_bytes): """Given the raw bytes containing the property-flags and property-list from a content-frame-header, parse and insert into a dictionary stored in this object as an attribute named 'properties'.""" r = AMQPReader(raw_bytes) # # Read 16-bit shorts until we get one with a low bit set to zero # flags = [] while 1: flag_bits = r.read_short() flags.append(flag_bits) if flag_bits & 1 == 0: break shift = 0 d = {} for key, proptype in self.PROPERTIES: if shift == 0: if not flags: break flag_bits, flags = flags[0], flags[1:] shift = 15 if flag_bits & (1 << shift): d[key] = getattr(r, 'read_' + proptype)() shift -= 1 self.properties = d def _serialize_properties(self): """serialize the 'properties' attribute (a dictionary) into the raw bytes making up a set of property flags and a property list, suitable for putting into a content frame header.""" shift = 15 flag_bits = 0 flags = [] raw_bytes = AMQPWriter() for key, proptype in self.PROPERTIES: val = self.properties.get(key, None) if val is not None: if shift == 0: flags.append(flag_bits) flag_bits = 0 shift = 15 flag_bits |= (1 << shift) if proptype != 'bit': getattr(raw_bytes, 'write_' + proptype)(val) shift -= 1 flags.append(flag_bits) result = AMQPWriter() for flag_bits in flags: result.write_short(flag_bits) result.write(raw_bytes.getvalue()) return result.getvalue() amqp-1.4.9/amqp/tests/0000755000076500000000000000000012644064471015205 5ustar asksolwheel00000000000000amqp-1.4.9/amqp/tests/__init__.py0000644000076500000000000000000012603577354017310 0ustar asksolwheel00000000000000amqp-1.4.9/amqp/tests/case.py0000644000076500000000000000366512644026054016477 0ustar asksolwheel00000000000000from __future__ import absolute_import import sys from functools import wraps from io import StringIO import mock from nose import SkipTest # noqa try: import unittest unittest.skip except AttributeError: import unittest2 as unittest # noqa PY3 = sys.version_info[0] == 3 patch = mock.patch call = mock.call class Case(unittest.TestCase): def assertItemsEqual(self, a, b, *args, **kwargs): return self.assertEqual(sorted(a), sorted(b), *args, **kwargs) assertSameElements = assertItemsEqual class Mock(mock.Mock): def __init__(self, *args, **kwargs): attrs = kwargs.pop('attrs', None) or {} super(Mock, self).__init__(*args, **kwargs) for attr_name, attr_value in attrs.items(): setattr(self, attr_name, attr_value) class _ContextMock(Mock): """Dummy class implementing __enter__ and __exit__ as the with statement requires these to be implemented in the class, not just the instance.""" def __enter__(self): pass def __exit__(self, *exc_info): pass def ContextMock(*args, **kwargs): obj = _ContextMock(*args, **kwargs) obj.attach_mock(Mock(), '__enter__') obj.attach_mock(Mock(), '__exit__') obj.__enter__.return_value = obj # if __exit__ return a value the exception is ignored, # so it must return None here. obj.__exit__.return_value = None return obj class MockPool(object): def __init__(self, value=None): self.value = value or ContextMock() def acquire(self, **kwargs): return self.value def redirect_stdouts(fun): @wraps(fun) def _inner(*args, **kwargs): sys.stdout = StringIO() sys.stderr = StringIO() try: return fun(*args, **dict(kwargs, stdout=sys.stdout, stderr=sys.stderr)) finally: sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ return _inner amqp-1.4.9/amqp/tests/test_channel.py0000644000076500000000000000207712644026054020227 0ustar asksolwheel00000000000000from __future__ import absolute_import from collections import defaultdict from amqp.channel import Channel from amqp.exceptions import NotConfirmed from amqp.serialization import AMQPWriter, AMQPReader from amqp.tests.case import Case, Mock class NoOpenChannel(Channel): def _x_open(self): pass class test_Channel(Case): def setUp(self): self.args = AMQPWriter() self.connection = Mock(name='connection') self.connection.channels = defaultdict(lambda: None) self.channel = NoOpenChannel(self.connection, channel_id=1) def test_basic_nack(self, delivery_tag=3172312312): self.args.write_longlong(delivery_tag) self.args.write_bit(0) self.args.write_bit(0) with self.assertRaises(NotConfirmed): self.channel._basic_nack(AMQPReader(self.args.getvalue())) callback = Mock(name='callback') self.channel.events['basic_nack'].add(callback) self.channel._basic_nack(AMQPReader(self.args.getvalue())) callback.assert_called_with(delivery_tag, False, False) amqp-1.4.9/amqp/transport.py0000644000076500000000000002344412644026054016453 0ustar asksolwheel00000000000000# 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 absolute_import import errno import re import socket import ssl # Jython does not have this attribute try: from socket import SOL_TCP except ImportError: # pragma: no cover from socket import IPPROTO_TCP as SOL_TCP # noqa try: from ssl import SSLError except ImportError: class SSLError(Exception): # noqa pass from struct import pack, unpack from .exceptions import UnexpectedFrame from .utils import get_errno, set_cloexec _UNAVAIL = errno.EAGAIN, errno.EINTR, errno.ENOENT AMQP_PORT = 5672 EMPTY_BUFFER = bytes() # Yes, Advanced Message Queuing Protocol Protocol is redundant AMQP_PROTOCOL_HEADER = 'AMQP\x01\x01\x00\x09'.encode('latin_1') # Match things like: [fe80::1]:5432, from RFC 2732 IPV6_LITERAL = re.compile(r'\[([\.0-9a-f:]+)\](?::(\d+))?') class _AbstractTransport(object): """Common superclass for TCP and SSL transports""" connected = False def __init__(self, host, connect_timeout): self.connected = True msg = None port = AMQP_PORT m = IPV6_LITERAL.match(host) if m: host = m.group(1) if m.group(2): port = int(m.group(2)) else: if ':' in host: host, port = host.rsplit(':', 1) port = int(port) self.sock = None last_err = None for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, SOL_TCP): af, socktype, proto, canonname, sa = res try: self.sock = socket.socket(af, socktype, proto) try: set_cloexec(self.sock, True) except NotImplementedError: pass self.sock.settimeout(connect_timeout) self.sock.connect(sa) except socket.error as exc: msg = exc self.sock.close() self.sock = None last_err = msg continue break if not self.sock: # Didn't connect, return the most recent error message raise socket.error(last_err) try: self.sock.settimeout(None) self.sock.setsockopt(SOL_TCP, socket.TCP_NODELAY, 1) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) self._setup_transport() self._write(AMQP_PROTOCOL_HEADER) except (OSError, IOError, socket.error) as exc: if get_errno(exc) not in _UNAVAIL: self.connected = False raise def __del__(self): try: # socket module may have been collected by gc # if this is called by a thread at shutdown. if socket is not None: try: self.close() except socket.error: pass finally: self.sock = None def _read(self, n, initial=False): """Read exactly n bytes from the peer""" raise NotImplementedError('Must be overriden in subclass') def _setup_transport(self): """Do any additional initialization of the class (used by the subclasses).""" pass def _shutdown_transport(self): """Do any preliminary work in shutting down the connection.""" pass def _write(self, s): """Completely write a string to the peer.""" raise NotImplementedError('Must be overriden in subclass') def close(self): if self.sock is not None: self._shutdown_transport() # Call shutdown first to make sure that pending messages # reach the AMQP broker if the program exits after # calling this method. self.sock.shutdown(socket.SHUT_RDWR) self.sock.close() self.sock = None self.connected = False def read_frame(self, unpack=unpack): read = self._read read_frame_buffer = EMPTY_BUFFER try: frame_header = read(7, True) read_frame_buffer += frame_header frame_type, channel, size = unpack('>BHI', frame_header) payload = read(size) read_frame_buffer += payload ch = ord(read(1)) except socket.timeout: self._read_buffer = read_frame_buffer + self._read_buffer raise except (OSError, IOError, socket.error) as exc: # Don't disconnect for ssl read time outs # http://bugs.python.org/issue10272 if isinstance(exc, SSLError) and 'timed out' in str(exc): raise socket.timeout() if get_errno(exc) not in _UNAVAIL: self.connected = False raise if ch == 206: # '\xce' return frame_type, channel, payload else: raise UnexpectedFrame( 'Received 0x{0:02x} while expecting 0xce'.format(ch)) def write_frame(self, frame_type, channel, payload): size = len(payload) try: self._write(pack( '>BHI%dsB' % size, frame_type, channel, size, payload, 0xce, )) except socket.timeout: raise except (OSError, IOError, socket.error) as exc: if get_errno(exc) not in _UNAVAIL: self.connected = False raise class SSLTransport(_AbstractTransport): """Transport that works over SSL""" def __init__(self, host, connect_timeout, ssl): if isinstance(ssl, dict): self.sslopts = ssl self._read_buffer = EMPTY_BUFFER super(SSLTransport, self).__init__(host, connect_timeout) def _setup_transport(self): """Wrap the socket in an SSL object.""" if hasattr(self, 'sslopts'): self.sock = ssl.wrap_socket(self.sock, **self.sslopts) else: self.sock = ssl.wrap_socket(self.sock) self.sock.do_handshake() self._quick_recv = self.sock.read def _shutdown_transport(self): """Unwrap a Python 2.6 SSL socket, so we can call shutdown()""" if self.sock is not None: try: unwrap = self.sock.unwrap except AttributeError: return self.sock = unwrap() def _read(self, n, initial=False, _errnos=(errno.ENOENT, errno.EAGAIN, errno.EINTR)): # According to SSL_read(3), it can at most return 16kb of data. # Thus, we use an internal read buffer like TCPTransport._read # to get the exact number of bytes wanted. recv = self._quick_recv rbuf = self._read_buffer try: while len(rbuf) < n: try: s = recv(n - len(rbuf)) # see note above except socket.error as exc: # ssl.sock.read may cause ENOENT if the # operation couldn't be performed (Issue celery#1414). if not initial and exc.errno in _errnos: continue raise if not s: raise IOError('Socket closed') rbuf += s except: self._read_buffer = rbuf raise result, self._read_buffer = rbuf[:n], rbuf[n:] return result def _write(self, s): """Write a string out to the SSL socket fully.""" try: write = self.sock.write except AttributeError: # Works around a bug in python socket library raise IOError('Socket closed') else: while s: n = write(s) if not n: raise IOError('Socket closed') s = s[n:] class TCPTransport(_AbstractTransport): """Transport that deals directly with TCP socket.""" def _setup_transport(self): """Setup to _write() directly to the socket, and do our own buffered reads.""" self._write = self.sock.sendall self._read_buffer = EMPTY_BUFFER self._quick_recv = self.sock.recv def _read(self, n, initial=False, _errnos=(errno.EAGAIN, errno.EINTR)): """Read exactly n bytes from the socket""" recv = self._quick_recv rbuf = self._read_buffer try: while len(rbuf) < n: try: s = recv(n - len(rbuf)) except socket.error as exc: if not initial and exc.errno in _errnos: continue raise if not s: raise IOError('Socket closed') rbuf += s except: self._read_buffer = rbuf raise result, self._read_buffer = rbuf[:n], rbuf[n:] return result def create_transport(host, connect_timeout, ssl=False): """Given a few parameters from the Connection constructor, select and create a subclass of _AbstractTransport.""" if ssl: return SSLTransport(host, connect_timeout, ssl) else: return TCPTransport(host, connect_timeout) amqp-1.4.9/amqp/utils.py0000644000076500000000000000517512644026054015560 0ustar asksolwheel00000000000000from __future__ import absolute_import import sys try: import fcntl except ImportError: fcntl = None # noqa class promise(object): if not hasattr(sys, 'pypy_version_info'): __slots__ = tuple( 'fun args kwargs value ready failed ' ' on_success on_error calls'.split() ) def __init__(self, fun, args=(), kwargs=(), on_success=None, on_error=None): self.fun = fun self.args = args self.kwargs = kwargs self.ready = False self.failed = False self.on_success = on_success self.on_error = on_error self.value = None self.calls = 0 def __repr__(self): return '<$: {0.fun.__name__}(*{0.args!r}, **{0.kwargs!r})'.format( self, ) def __call__(self, *args, **kwargs): try: self.value = self.fun( *self.args + args if self.args else args, **dict(self.kwargs, **kwargs) if self.kwargs else kwargs ) except Exception as exc: self.set_error_state(exc) else: if self.on_success: self.on_success(self.value) finally: self.ready = True self.calls += 1 def then(self, callback=None, on_error=None): self.on_success = callback self.on_error = on_error return callback def set_error_state(self, exc): self.failed = True if self.on_error is None: raise self.on_error(exc) def throw(self, exc): try: raise exc except exc.__class__ as with_cause: self.set_error_state(with_cause) def noop(): return promise(lambda *a, **k: None) try: from os import set_cloexec # Python 3.4? except ImportError: def set_cloexec(fd, cloexec): # noqa try: FD_CLOEXEC = fcntl.FD_CLOEXEC except AttributeError: raise NotImplementedError( 'close-on-exec flag not supported on this platform', ) flags = fcntl.fcntl(fd, fcntl.F_GETFD) if cloexec: flags |= FD_CLOEXEC else: flags &= ~FD_CLOEXEC return fcntl.fcntl(fd, fcntl.F_SETFD, flags) def get_errno(exc): """:exc:`socket.error` and :exc:`IOError` first got the ``.errno`` attribute in Py2.7""" try: return exc.errno except AttributeError: try: # e.args = (errno, reason) if isinstance(exc.args, tuple) and len(exc.args) == 2: return exc.args[0] except AttributeError: pass return 0 amqp-1.4.9/amqp.egg-info/0000755000076500000000000000000012644064471015535 5ustar asksolwheel00000000000000amqp-1.4.9/amqp.egg-info/dependency_links.txt0000644000076500000000000000000112644064463021604 0ustar asksolwheel00000000000000 amqp-1.4.9/amqp.egg-info/not-zip-safe0000644000076500000000000000000112644064463017764 0ustar asksolwheel00000000000000 amqp-1.4.9/amqp.egg-info/PKG-INFO0000644000076500000000000001331512644064463016636 0ustar asksolwheel00000000000000Metadata-Version: 1.1 Name: amqp Version: 1.4.9 Summary: Low-level AMQP client for Python (fork of amqplib) Home-page: http://github.com/celery/py-amqp Author: Ask Solem Author-email: pyamqp@celeryproject.org License: LGPL Description: ===================================================================== Python AMQP 0.9.1 client library ===================================================================== :Version: 1.4.9 :Web: http://amqp.readthedocs.org/ :Download: http://pypi.python.org/pypi/amqp/ :Source: http://github.com/celery/py-amqp/ :Keywords: amqp, rabbitmq About ===== This is a fork of amqplib_ which was originally written by Barry Pederson. It is maintained by the Celery_ project, and used by `kombu`_ as a pure python alternative when `librabbitmq`_ is not available. This library should be API compatible with `librabbitmq`_. .. _amqplib: http://pypi.python.org/pypi/amqplib .. _Celery: http://celeryproject.org/ .. _kombu: http://kombu.readthedocs.org/ .. _librabbitmq: http://pypi.python.org/pypi/librabbitmq Differences from `amqplib`_ =========================== - Supports draining events from multiple channels (``Connection.drain_events``) - Support for timeouts - Channels are restored after channel error, instead of having to close the connection. - Support for heartbeats - ``Connection.heartbeat_tick(rate=2)`` must called at regular intervals (half of the heartbeat value if rate is 2). - Or some other scheme by using ``Connection.send_heartbeat``. - Supports RabbitMQ extensions: - Consumer Cancel Notifications - by default a cancel results in ``ChannelError`` being raised - but not if a ``on_cancel`` callback is passed to ``basic_consume``. - Publisher confirms - ``Channel.confirm_select()`` enables publisher confirms. - ``Channel.events['basic_ack'].append(my_callback)`` adds a callback to be called when a message is confirmed. This callback is then called with the signature ``(delivery_tag, multiple)``. - Exchange-to-exchange bindings: ``exchange_bind`` / ``exchange_unbind``. - ``Channel.confirm_select()`` enables publisher confirms. - ``Channel.events['basic_ack'].append(my_callback)`` adds a callback to be called when a message is confirmed. This callback is then called with the signature ``(delivery_tag, multiple)``. - Support for ``basic_return`` - Uses AMQP 0-9-1 instead of 0-8. - ``Channel.access_request`` and ``ticket`` arguments to methods **removed**. - Supports the ``arguments`` argument to ``basic_consume``. - ``internal`` argument to ``exchange_declare`` removed. - ``auto_delete`` argument to ``exchange_declare`` deprecated - ``insist`` argument to ``Connection`` removed. - ``Channel.alerts`` has been removed. - Support for ``Channel.basic_recover_async``. - ``Channel.basic_recover`` deprecated. - Exceptions renamed to have idiomatic names: - ``AMQPException`` -> ``AMQPError`` - ``AMQPConnectionException`` -> ConnectionError`` - ``AMQPChannelException`` -> ChannelError`` - ``Connection.known_hosts`` removed. - ``Connection`` no longer supports redirects. - ``exchange`` argument to ``queue_bind`` can now be empty to use the "default exchange". - Adds ``Connection.is_alive`` that tries to detect whether the connection can still be used. - Adds ``Connection.connection_errors`` and ``.channel_errors``, a list of recoverable errors. - Exposes the underlying socket as ``Connection.sock``. - Adds ``Channel.no_ack_consumers`` to keep track of consumer tags that set the no_ack flag. - Slightly better at error recovery Further ======= - Differences between AMQP 0.8 and 0.9.1 http://www.rabbitmq.com/amqp-0-8-to-0-9-1.html - AMQP 0.9.1 Quick Reference http://www.rabbitmq.com/amqp-0-9-1-quickref.html - RabbitMQ Extensions http://www.rabbitmq.com/extensions.html - For more information about AMQP, visit http://www.amqp.org - For other Python client libraries see: http://www.rabbitmq.com/devtools.html#python-dev .. image:: https://d2weczhvl823v0.cloudfront.net/celery/celery/trend.png :alt: Bitdeli badge :target: https://bitdeli.com/free Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.0 Classifier: Programming Language :: Python :: 3.1 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent amqp-1.4.9/amqp.egg-info/SOURCES.txt0000644000076500000000000000327212644064463017426 0ustar asksolwheel00000000000000Changelog LICENSE MANIFEST.in README.rst setup.cfg setup.py amqp/__init__.py amqp/abstract_channel.py amqp/basic_message.py amqp/channel.py amqp/connection.py amqp/exceptions.py amqp/five.py amqp/method_framing.py amqp/protocol.py amqp/serialization.py amqp/transport.py amqp/utils.py amqp.egg-info/PKG-INFO amqp.egg-info/SOURCES.txt amqp.egg-info/dependency_links.txt amqp.egg-info/not-zip-safe amqp.egg-info/top_level.txt amqp/tests/__init__.py amqp/tests/case.py amqp/tests/test_channel.py demo/amqp_clock.py demo/demo_receive.py demo/demo_send.py docs/Makefile docs/changelog.rst docs/conf.py docs/index.rst docs/.static/.keep docs/.templates/page.html docs/.templates/sidebarintro.html docs/.templates/sidebarlogo.html docs/_ext/applyxrefs.py docs/_ext/literals_to_xrefs.py docs/_theme/celery/theme.conf docs/_theme/celery/static/celery.css_t docs/includes/intro.txt docs/reference/amqp.abstract_channel.rst docs/reference/amqp.basic_message.rst docs/reference/amqp.channel.rst docs/reference/amqp.connection.rst docs/reference/amqp.exceptions.rst docs/reference/amqp.five.rst docs/reference/amqp.method_framing.rst docs/reference/amqp.protocol.rst docs/reference/amqp.serialization.rst docs/reference/amqp.transport.rst docs/reference/amqp.utils.rst docs/reference/index.rst docs/templates/readme.txt extra/README extra/generate_skeleton_0_8.py extra/update_comments_from_spec.py extra/release/bump_version.py extra/release/sphinx-to-rst.py funtests/run_all.py funtests/settings.py funtests/test_basic_message.py funtests/test_channel.py funtests/test_connection.py funtests/test_exceptions.py funtests/test_serialization.py funtests/test_with.py requirements/docs.txt requirements/pkgutils.txt requirements/test.txtamqp-1.4.9/amqp.egg-info/top_level.txt0000644000076500000000000000000512644064463020263 0ustar asksolwheel00000000000000amqp amqp-1.4.9/Changelog0000644000076500000000000003224712644064263014726 0ustar asksolwheel00000000000000Changes ======= py-amqp is fork of amqplib used by Kombu containing additional features and improvements. The previous amqplib changelog is here: http://code.google.com/p/py-amqplib/source/browse/CHANGES .. _version-1.4.9: 1.4.9 ===== :release-date: 2016-01-08 5:50 PM PST - Fixes compatibility with Linux/OS X instances where the ``ctypes`` module does not exist. Fix contributed by Jared Lewis. .. _version-1.4.8: 1.4.8 ===== :release-date: 2015-12-07 12:25 AM :release-by: Ask Solem - ``abstract_channel.wait`` now accepts a float `timeout` parameter expressed in seconds Contributed by Goir. .. _version-1.4.7: 1.4.7 ===== :release-date: 2015-10-02 05:30 P.M PDT :release-by: Ask Solem - Fixed libSystem error on OS X 10.11 (El Capitan) Fix contributed by Eric Wang. - ``channel.basic_publish`` now raises :exc:`amqp.exceptions.NotConfirmed` on ``basic.nack``. - AMQP timestamps received are now converted from GMT instead of local time (Issue #67). - Wheel package installation now supported by both Python 2 and Python3. Fix contributed by Rémy Greinhofer. .. _version-1.4.6: 1.4.6 ===== :release-date: 2014-08-11 06:00 P.M UTC :release-by: Ask Solem - Now keeps buffer when socket times out. Fix contributed by Artyom Koval. - Adds ``Connection.Transport`` attribute that can be used to specify a different transport implementation. Contributed by Yury Selivanov. .. _version-1.4.5: 1.4.5 ===== :release-date: 2014-04-15 09:00 P.M UTC :release-by: Ask Solem - Can now deserialize more AMQP types. Now handles types ``short string``, ``short short int``, ``short short unsigned int``, ``short int``, ``short unsigned int``, ``long unsigned int``, ``long long int``, ``long long unsigned int`` and ``float`` which for some reason was missing, even in the original amqplib module. - SSL: Workaround for Python SSL bug. A bug in the python socket library causes ``ssl.read/write()`` on a closed socket to raise :exc:`AttributeError` instead of :exc:`IOError`. Fix contributed by Craig Jellick. - ``Transport.__del_`` now handles errors occurring at late interpreter shutdown (Issue #36). .. _version-1.4.4: 1.4.4 ===== :release-date: 2014-03-03 04:00 P.M UTC :release-by: Ask Solem - SSL transport accidentally disconnected after read timeout. Fix contributed by Craig Jellick. .. _version-1.4.3: 1.4.3 ===== :release-date: 2014-02-09 03:00 P.M UTC :release-by: Ask Solem - Fixed bug where more data was requested from the socket than was actually needed. Contributed by Ionel Cristian Mărieș. .. _version-1.4.2: 1.4.2 ===== :release-date: 2014-01-23 05:00 P.M UTC - Heartbeat negotiation would use heartbeat value from server even if heartbeat disabled (Issue #31). .. _version-1.4.1: 1.4.1 ===== :release-date: 2014-01-14 09:30 P.M UTC :release-by: Ask Solem - Fixed error occurring when heartbeats disabled. .. _version-1.4.0: 1.4.0 ===== :release-date: 2014-01-13 03:00 P.M UTC :release-by: Ask Solem - Heartbeat implementation improved (Issue #6). The new heartbeat behavior is the same approach as taken by the RabbitMQ java library. This also means that clients should preferably call the ``heartbeat_tick`` method more frequently (like every second) instead of using the old ``rate`` argument (which is now ignored). - Heartbeat interval is negotiated with the server. - Some delay is allowed if the heartbeat is late. - Monotonic time is used to keep track of the heartbeat instead of relying on the caller to call the checking function at the right time. Contributed by Dustin J. Mitchell. - NoneType is now supported in tables and arrays. Contributed by Dominik Fässler. - SSLTransport: Now handles ``ENOENT``. Fix contributed by Adrien Guinet. .. _version-1.3.3: 1.3.3 ===== :release-date: 2013-11-11 03:30 P.M UTC :release-by: Ask Solem - SSLTransport: Now keeps read buffer if an exception is raised (Issue #26). Fix contributed by Tommie Gannert. .. _version-1.3.2: 1.3.2 ===== :release-date: 2013-10-29 02:00 P.M UTC :release-by: Ask Solem - Message.channel is now a channel object (not the channel id). - Bug in previous version caused the socket to be flagged as disconnected at EAGAIN/EINTR. .. _version-1.3.1: 1.3.1 ===== :release-date: 2013-10-24 04:00 P.M UTC :release-by: Ask Solem - Now implements Connection.connected (Issue #22). - Fixed bug where ``str(AMQPError)`` did not return string. .. _version-1.3.0: 1.3.0 ===== :release-date: 2013-09-04 02:39 P.M UTC :release-by: Ask Solem - Now sets ``Message.channel`` on delivery (Issue #12) amqplib used to make the channel object available as ``Message.delivery_info['channel']``, but this was removed in py-amqp. librabbitmq sets ``Message.channel``, which is a more reasonable solution in our opinion as that keeps the delivery info intact. - New option to wait for publish confirmations (Issue #3) There is now a new Connection ``confirm_publish`` that will force any ``basic_publish`` call to wait for confirmation. Enabling publisher confirms like this degrades performance considerably, but can be suitable for some applications and now it's possible by configuration. - ``queue_declare`` now returns named tuple of type :class:`~amqp.protocol.basic_declare_ok_t`. Supporting fields: ``queue``, ``message_count``, and ``consumer_count``. - Contents of ``Channel.returned_messages`` is now named tuples. Supporting fields: ``reply_code``, ``reply_text``, ``exchange``, ``routing_key``, and ``message``. - Sockets now set to close on exec using the ``FD_CLOEXEC`` flag. Currently only supported on platforms supporting this flag, which does not include Windows. Contributed by Tommie Gannert. .. _version-1.2.1: 1.2.1 ===== :release-date: 2013-08-16 05:30 P.M UTC :release-by: Ask Solem - Adds promise type: :meth:`amqp.utils.promise` - Merges fixes from 1.0.x .. _version-1.2.0: 1.2.0 ===== :release-date: 2012-11-12 04:00 P.M UTC :release-by: Ask Solem - New exception hierarchy: - :class:`~amqp.AMQPError` - :class:`~amqp.ConnectionError` - :class:`~amqp.RecoverableConnectionError` - :class:`~amqp.ConsumerCancelled` - :class:`~amqp.ConnectionForced` - :class:`~amqp.ResourceError` - :class:`~IrrecoverableConnectionError` - :class:`~amqp.ChannelNotOpen` - :class:`~amqp.FrameError` - :class:`~amqp.FrameSyntaxError` - :class:`~amqp.InvalidCommand` - :class:`~amqp.InvalidPath` - :class:`~amqp.NotAllowed` - :class:`~amqp.UnexpectedFrame` - :class:`~amqp.AMQPNotImplementedError` - :class:`~amqp.InternalError` - :class:`~amqp.ChannelError` - :class:`~RecoverableChannelError` - :class:`~amqp.ContentTooLarge` - :class:`~amqp.NoConsumers` - :class:`~amqp.ResourceLocked` - :class:`~IrrecoverableChannelError` - :class:`~amqp.AccessRefused` - :class:`~amqp.NotFound` - :class:`~amqp.PreconditionFailed` .. _version-1.1.0: 1.1.0 ===== :release-date: 2013-11-08 10:36 P.M UTC :release-by: Ask Solem - No longer supports Python 2.5 - Fixed receiving of float table values. - Now Supports Python 3 and Python 2.6+ in the same source code. - Python 3 related fixes. .. _version-1.0.13: 1.0.13 ====== :release-date: 2013-07-31 04:00 P.M BST :release-by: Ask Solem - Fixed problems with the SSL transport (Issue #15). Fix contributed by Adrien Guinet. - Small optimizations .. _version-1.0.12: 1.0.12 ====== :release-date: 2013-06-25 02:00 P.M BST :release-by: Ask Solem - Fixed another Python 3 compatibility problem. .. _version-1.0.11: 1.0.11 ====== :release-date: 2013-04-11 06:00 P.M BST :release-by: Ask Solem - Fixed Python 3 incompatibility in ``amqp/transport.py``. .. _version-1.0.10: 1.0.10 ====== :release-date: 2013-03-21 03:30 P.M UTC :release-by: Ask Solem - Fixed Python 3 incompatibility in ``amqp/serialization.py``. (Issue #11). .. _version-1.0.9: 1.0.9 ===== :release-date: 2013-03-08 10:40 A.M UTC :release-by: Ask Solem - Publisher ack callbacks should now work after typo fix (Issue #9). - ``channel(explicit_id)`` will now claim that id from the array of unused channel ids. - Fixes Jython compatibility. .. _version-1.0.8: 1.0.8 ===== :release-date: 2013-02-08 01:00 P.M UTC :release-by: Ask Solem - Fixed SyntaxError on Python 2.5 .. _version-1.0.7: 1.0.7 ===== :release-date: 2013-02-08 01:00 P.M UTC :release-by: Ask Solem - Workaround for bug on some Python 2.5 installations where (2**32) is 0. - Can now serialize the ARRAY type. Contributed by Adam Wentz. - Fixed tuple format bug in exception (Issue #4). .. _version-1.0.6: 1.0.6 ===== :release-date: 2012-11-29 01:14 P.M UTC :release-by: Ask Solem - ``Channel.close`` is now ignored if the connection attribute is None. .. _version-1.0.5: 1.0.5 ===== :release-date: 2012-11-21 04:00 P.M UTC :release-by: Ask Solem - ``Channel.basic_cancel`` is now ignored if the channel was already closed. - ``Channel.events`` is now a dict of sets:: >>> channel.events['basic_return'].add(on_basic_return) >>> channel.events['basic_return'].discard(on_basic_return) .. _version-1.0.4: 1.0.4 ===== :release-date: 2012-11-13 04:00 P.M UTC :release-by: Ask Solem - Fixes Python 2.5 support .. _version-1.0.3: 1.0.3 ===== :release-date: 2012-11-12 04:00 P.M UTC :release-by: Ask Solem - Now can also handle float in headers/tables when receiving messages. - Now uses :class:`array.array` to keep track of unused channel ids. - The :data:`~amqp.exceptions.METHOD_NAME_MAP` has been updated for amqp/0.9.1 and Rabbit extensions. - Removed a bunch of accidentally included images. .. _version-1.0.2: 1.0.2 ===== :release-date: 2012-11-06 05:00 P.M UTC :release-by: Ask Solem - Now supports float values in headers/tables. .. _version-1.0.1: 1.0.1 ===== :release-date: 2012-11-05 01:00 P.M UTC :release-by: Ask Solem - Connection errors no longer includes :exc:`AttributeError`. - Fixed problem with using the SSL transport in a non-blocking context. Fix contributed by Mher Movsisyan. .. _version-1.0.0: 1.0.0 ===== :release-date: 2012-11-05 01:00 P.M UTC :release-by: Ask Solem - Channels are now restored on channel error, so that the connection does not have to closed. .. _version-0.9.4: Version 0.9.4 ============= - Adds support for ``exchange_bind`` and ``exchange_unbind``. Contributed by Rumyana Neykova - Fixed bugs in funtests and demo scripts. Contributed by Rumyana Neykova .. _version-0.9.3: Version 0.9.3 ============= - Fixed bug that could cause the consumer to crash when reading large message payloads asynchronously. - Serialization error messages now include the invalid value. .. _version-0.9.2: Version 0.9.2 ============= - Consumer cancel notification support was broken (Issue #1) Fix contributed by Andrew Grangaard .. _version-0.9.1: Version 0.9.1 ============= - Supports draining events from multiple channels (``Connection.drain_events``) - Support for timeouts - Support for heartbeats - ``Connection.heartbeat_tick(rate=2)`` must called at regular intervals (half of the heartbeat value if rate is 2). - Or some other scheme by using ``Connection.send_heartbeat``. - Supports RabbitMQ extensions: - Consumer Cancel Notifications - by default a cancel results in ``ChannelError`` being raised - but not if a ``on_cancel`` callback is passed to ``basic_consume``. - Publisher confirms - ``Channel.confirm_select()`` enables publisher confirms. - ``Channel.events['basic_ack'].append(my_callback)`` adds a callback to be called when a message is confirmed. This callback is then called with the signature ``(delivery_tag, multiple)``. - Support for ``basic_return`` - Uses AMQP 0-9-1 instead of 0-8. - ``Channel.access_request`` and ``ticket`` arguments to methods **removed**. - Supports the ``arguments`` argument to ``basic_consume``. - ``internal`` argument to ``exchange_declare`` removed. - ``auto_delete`` argument to ``exchange_declare`` deprecated - ``insist`` argument to ``Connection`` removed. - ``Channel.alerts`` has been removed. - Support for ``Channel.basic_recover_async``. - ``Channel.basic_recover`` deprecated. - Exceptions renamed to have idiomatic names: - ``AMQPException`` -> ``AMQPError`` - ``AMQPConnectionException`` -> ConnectionError`` - ``AMQPChannelException`` -> ChannelError`` - ``Connection.known_hosts`` removed. - ``Connection`` no longer supports redirects. - ``exchange`` argument to ``queue_bind`` can now be empty to use the "default exchange". - Adds ``Connection.is_alive`` that tries to detect whether the connection can still be used. - Adds ``Connection.connection_errors`` and ``.channel_errors``, a list of recoverable errors. - Exposes the underlying socket as ``Connection.sock``. - Adds ``Channel.no_ack_consumers`` to keep track of consumer tags that set the no_ack flag. - Slightly better at error recovery amqp-1.4.9/demo/0000755000076500000000000000000012644064471014031 5ustar asksolwheel00000000000000amqp-1.4.9/demo/amqp_clock.py0000755000076500000000000000423012644026054016511 0ustar asksolwheel00000000000000#!/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 amqp Message = amqp.Message EXCHANGE_NAME = 'clock' TOPIC_PATTERN = '%Y.%m.%d.%w.%H.%M' # Python datetime.strftime() pattern def main(): parser = OptionParser() parser.add_option( '--host', dest='host', help='AMQP server to connect to (default: %default)', default='localhost', ) parser.add_option( '-u', '--userid', dest='userid', help='AMQP userid to authenticate as (default: %default)', default='guest', ) parser.add_option( '-p', '--password', dest='password', help='AMQP password to authenticate with (default: %default)', default='guest', ) parser.add_option( '--ssl', dest='ssl', action='store_true', help='Enable SSL with AMQP server (default: not enabled)', default=False, ) options, args = parser.parse_args() conn = amqp.Connection(options.host, options.userid, options.password) ch = conn.channel() ch.exchange_declare(EXCHANGE_NAME, type='topic') # Make sure our first message is close to the beginning # of a minute now = datetime.now() if now.second > 0: sleep(60 - now.second) while True: now = datetime.now() msg = Message(timestamp=now) msg_topic = now.strftime(TOPIC_PATTERN) ch.basic_publish(msg, EXCHANGE_NAME, routing_key=msg_topic) # Don't know how long the basic_publish took, so # grab the time again. now = datetime.now() sleep(60 - now.second) ch.close() conn.close() if __name__ == '__main__': main() amqp-1.4.9/demo/demo_receive.py0000755000076500000000000000366512644026054017041 0ustar asksolwheel00000000000000#!/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 from functools import partial import amqp def callback(channel, msg): for key, val in msg.properties.items(): print('%s: %s' % (key, str(val))) for key, val in msg.delivery_info.items(): print('> %s: %s' % (key, str(val))) print('') print(msg.body) print('-------') print(msg.delivery_tag) channel.basic_ack(msg.delivery_tag) # # Cancel this callback # if msg.body == 'quit': channel.basic_cancel(msg.consumer_tag) def main(): parser = OptionParser() parser.add_option( '--host', dest='host', help='AMQP server to connect to (default: %default)', default='localhost', ) parser.add_option( '-u', '--userid', dest='userid', help='userid to authenticate as (default: %default)', default='guest', ) parser.add_option( '-p', '--password', dest='password', help='password to authenticate with (default: %default)', default='guest', ) parser.add_option( '--ssl', dest='ssl', action='store_true', help='Enable SSL (default: not enabled)', default=False, ) options, args = parser.parse_args() conn = amqp.Connection(options.host, userid=options.userid, password=options.password, ssl=options.ssl) ch = conn.channel() ch.exchange_declare('myfan', 'fanout') qname, _, _ = ch.queue_declare() ch.queue_bind(qname, 'myfan') ch.basic_consume(qname, callback=partial(callback, ch)) #pyamqp:// # # Loop as long as the channel has callbacks registered # while ch.callbacks: ch.wait() ch.close() conn.close() if __name__ == '__main__': main() amqp-1.4.9/demo/demo_send.py0000755000076500000000000000312212644026054016334 0ustar asksolwheel00000000000000#!/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 from optparse import OptionParser import amqp def main(): parser = OptionParser( usage='usage: %prog [options] message\nexample: %prog hello world', ) parser.add_option( '--host', dest='host', help='AMQP server to connect to (default: %default)', default='localhost', ) parser.add_option( '-u', '--userid', dest='userid', help='userid to authenticate as (default: %default)', default='guest', ) parser.add_option( '-p', '--password', dest='password', help='password to authenticate with (default: %default)', default='guest', ) parser.add_option( '--ssl', dest='ssl', action='store_true', help='Enable SSL (default: not enabled)', default=False, ) options, args = parser.parse_args() if not args: parser.print_help() sys.exit(1) msg_body = ' '.join(args) conn = amqp.Connection(options.host, userid=options.userid, password=options.password, ssl=options.ssl) ch = conn.channel() ch.exchange_declare('myfan', 'fanout') msg = amqp.Message(msg_body, content_type='text/plain', application_headers={'foo': 7, 'bar': 'baz'}) ch.basic_publish(msg, 'myfan') ch.close() conn.close() if __name__ == '__main__': main() amqp-1.4.9/docs/0000755000076500000000000000000012644064471014035 5ustar asksolwheel00000000000000amqp-1.4.9/docs/.static/0000755000076500000000000000000012644064471015402 5ustar asksolwheel00000000000000amqp-1.4.9/docs/.static/.keep0000644000076500000000000000000012270020623016277 0ustar asksolwheel00000000000000amqp-1.4.9/docs/.templates/0000755000076500000000000000000012644064471016111 5ustar asksolwheel00000000000000amqp-1.4.9/docs/.templates/page.html0000644000076500000000000000011312270020623017670 0ustar asksolwheel00000000000000{% extends "layout.html" %} {% block body %} {{ body }} {% endblock %} amqp-1.4.9/docs/.templates/sidebarintro.html0000644000076500000000000000024412270020623021446 0ustar asksolwheel00000000000000 amqp-1.4.9/docs/.templates/sidebarlogo.html0000644000076500000000000000024312270020623021252 0ustar asksolwheel00000000000000 amqp-1.4.9/docs/_ext/0000755000076500000000000000000012644064471014774 5ustar asksolwheel00000000000000amqp-1.4.9/docs/_ext/applyxrefs.py0000644000076500000000000000413512644026054017541 0ustar asksolwheel00000000000000"""Adds xref targets to the top of files.""" import sys import os testing = False DONT_TOUCH = ( './index.txt', ) def target_name(fn): if fn.endswith('.txt'): fn = fn[:-4] return '_' + fn.lstrip('./').replace('/', '-') def process_file(fn, lines): lines.insert(0, '\n') lines.insert(0, '.. %s:\n' % target_name(fn)) try: f = open(fn, 'w') except IOError: print("Can't open %s for writing. Not touching it." % fn) return try: f.writelines(lines) except IOError: print("Can't write to %s. Not touching it." % fn) finally: f.close() def has_target(fn): try: f = open(fn, 'r') except IOError: print("Can't open %s. Not touching it." % fn) return (True, None) readok = True try: lines = f.readlines() except IOError: print("Can't read %s. Not touching it." % fn) readok = False finally: f.close() if not readok: return (True, None) #print fn, len(lines) if len(lines) < 1: print("Not touching empty file %s." % fn) return (True, None) if lines[0].startswith('.. _'): return (True, None) return (False, lines) def main(argv=None): if argv is None: argv = sys.argv if len(argv) == 1: argv.extend('.') files = [] for root in argv[1:]: for (dirpath, dirnames, filenames) in os.walk(root): files.extend([(dirpath, f) for f in filenames]) files.sort() files = [os.path.join(p, fn) for p, fn in files if fn.endswith('.txt')] #print files for fn in files: if fn in DONT_TOUCH: print("Skipping blacklisted file %s." % fn) continue target_found, lines = has_target(fn) if not target_found: if testing: print '%s: %s' % (fn, lines[0]), else: print "Adding xref to %s" % fn process_file(fn, lines) else: print "Skipping %s: already has a xref" % fn if __name__ == '__main__': sys.exit(main()) amqp-1.4.9/docs/_ext/literals_to_xrefs.py0000644000076500000000000001115312270020623021061 0ustar asksolwheel00000000000000""" Runs through a reST file looking for old-style literals, and helps replace them with new-style references. """ import re import sys import shelve refre = re.compile(r'``([^`\s]+?)``') ROLES = ( 'attr', 'class', "djadmin", 'data', 'exc', 'file', 'func', 'lookup', 'meth', 'mod', "djadminopt", "ref", "setting", "term", "tfilter", "ttag", # special "skip", ) ALWAYS_SKIP = [ "NULL", "True", "False", ] def fixliterals(fname): data = open(fname).read() last = 0 new = [] storage = shelve.open("/tmp/literals_to_xref.shelve") lastvalues = storage.get("lastvalues", {}) for m in refre.finditer(data): new.append(data[last:m.start()]) last = m.end() line_start = data.rfind("\n", 0, m.start()) line_end = data.find("\n", m.end()) prev_start = data.rfind("\n", 0, line_start) next_end = data.find("\n", line_end + 1) # Skip always-skip stuff if m.group(1) in ALWAYS_SKIP: new.append(m.group(0)) continue # skip when the next line is a title next_line = data[m.end():next_end].strip() if next_line[0] in "!-/:-@[-`{-~" and \ all(c == next_line[0] for c in next_line): new.append(m.group(0)) continue sys.stdout.write("\n" + "-" * 80 + "\n") sys.stdout.write(data[prev_start + 1:m.start()]) sys.stdout.write(colorize(m.group(0), fg="red")) sys.stdout.write(data[m.end():next_end]) sys.stdout.write("\n\n") replace_type = None while replace_type is None: replace_type = raw_input( colorize("Replace role: ", fg="yellow")).strip().lower() if replace_type and replace_type not in ROLES: replace_type = None if replace_type == "": new.append(m.group(0)) continue if replace_type == "skip": new.append(m.group(0)) ALWAYS_SKIP.append(m.group(1)) continue default = lastvalues.get(m.group(1), m.group(1)) if default.endswith("()") and \ replace_type in ("class", "func", "meth"): default = default[:-2] replace_value = raw_input( colorize("Text [", fg="yellow") + default + colorize("]: ", fg="yellow")).strip() if not replace_value: replace_value = default new.append(":%s:`%s`" % (replace_type, replace_value)) lastvalues[m.group(1)] = replace_value new.append(data[last:]) open(fname, "w").write("".join(new)) storage["lastvalues"] = lastvalues storage.close() def colorize(text='', opts=(), **kwargs): """ Returns your text, enclosed in ANSI graphics codes. Depends on the keyword arguments 'fg' and 'bg', and the contents of the opts tuple/list. Returns the RESET code if no parameters are given. Valid colors: 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white' Valid options: 'bold' 'underscore' 'blink' 'reverse' 'conceal' 'noreset' - string will not be auto-terminated with the RESET code Examples: colorize('hello', fg='red', bg='blue', opts=('blink',)) colorize() colorize('goodbye', opts=('underscore',)) print colorize('first line', fg='red', opts=('noreset',)) print 'this should be red too' print colorize('and so should this') print 'this should not be red' """ color_names = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white') foreground = dict([(color_names[x], '3%s' % x) for x in range(8)]) background = dict([(color_names[x], '4%s' % x) for x in range(8)]) RESET = '0' opt_dict = {'bold': '1', 'underscore': '4', 'blink': '5', 'reverse': '7', 'conceal': '8'} text = str(text) code_list = [] if text == '' and len(opts) == 1 and opts[0] == 'reset': return '\x1b[%sm' % RESET for k, v in kwargs.iteritems(): if k == 'fg': code_list.append(foreground[v]) elif k == 'bg': code_list.append(background[v]) for o in opts: if o in opt_dict: code_list.append(opt_dict[o]) if 'noreset' not in opts: text = text + '\x1b[%sm' % RESET return ('\x1b[%sm' % ';'.join(code_list)) + text if __name__ == '__main__': try: fixliterals(sys.argv[1]) except (KeyboardInterrupt, SystemExit): print amqp-1.4.9/docs/_theme/0000755000076500000000000000000012644064471015276 5ustar asksolwheel00000000000000amqp-1.4.9/docs/_theme/celery/0000755000076500000000000000000012644064471016561 5ustar asksolwheel00000000000000amqp-1.4.9/docs/_theme/celery/static/0000755000076500000000000000000012644064471020050 5ustar asksolwheel00000000000000amqp-1.4.9/docs/_theme/celery/static/celery.css_t0000644000076500000000000001503412270020623022355 0ustar asksolwheel00000000000000/* * celery.css_t * ~~~~~~~~~~~~ * * :copyright: Copyright 2010 by Armin Ronacher. * :license: BSD, see LICENSE for details. */ {% set page_width = 940 %} {% set sidebar_width = 220 %} {% set body_font_stack = 'Optima, Segoe, "Segoe UI", Candara, Calibri, Arial, sans-serif' %} {% set headline_font_stack = 'Futura, "Trebuchet MS", Arial, sans-serif' %} {% set code_font_stack = "'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace" %} @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: {{ body_font_stack }}; font-size: 17px; background-color: white; color: #000; margin: 30px 0 0 0; padding: 0; } div.document { width: {{ page_width }}px; margin: 0 auto; } div.deck { font-size: 18px; } p.developmentversion { color: red; } div.related { width: {{ page_width - 20 }}px; padding: 5px 10px; background: #F2FCEE; margin: 15px auto 15px auto; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 0 0 0 {{ sidebar_width }}px; } div.sphinxsidebar { width: {{ sidebar_width }}px; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #ffffff; color: #3E4349; padding: 0 30px 0 30px; } img.celerylogo { padding: 0 0 10px 10px; float: right; } div.footer { width: {{ page_width - 15 }}px; margin: 10px auto 30px auto; padding-right: 15px; font-size: 14px; color: #888; text-align: right; } div.footer a { color: #888; } div.sphinxsidebar a { color: #444; text-decoration: none; border-bottom: 1px dashed #DCF0D5; } div.sphinxsidebar a:hover { border-bottom: 1px solid #999; } div.sphinxsidebar { font-size: 14px; line-height: 1.5; } div.sphinxsidebarwrapper { padding: 7px 10px; } div.sphinxsidebarwrapper p.logo { padding: 0 0 20px 0; margin: 0; } div.sphinxsidebar h3, div.sphinxsidebar h4 { font-family: {{ headline_font_stack }}; color: #444; font-size: 24px; font-weight: normal; margin: 0 0 5px 0; padding: 0; } div.sphinxsidebar h4 { font-size: 20px; } div.sphinxsidebar h3 a { color: #444; } div.sphinxsidebar p.logo a, div.sphinxsidebar h3 a, div.sphinxsidebar p.logo a:hover, div.sphinxsidebar h3 a:hover { border: none; } div.sphinxsidebar p { color: #555; margin: 10px 0; } div.sphinxsidebar ul { margin: 10px 0; padding: 0; color: #000; } div.sphinxsidebar input { border: 1px solid #ccc; font-family: {{ body_font_stack }}; font-size: 1em; } /* -- body styles ----------------------------------------------------------- */ a { color: #348613; text-decoration: underline; } a:hover { color: #59B833; text-decoration: underline; } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: {{ headline_font_stack }}; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; } div.body h1 { margin-top: 0; padding-top: 0; font-size: 200%; } div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } div.body h1 a.toc-backref, div.body h2 a.toc-backref, div.body h3 a.toc-backref, div.body h4 a.toc-backref, div.body h5 a.toc-backref, div.body h6 a.toc-backref { color: inherit!important; text-decoration: none; } a.headerlink { color: #ddd; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #eaeaea; } div.body p, div.body dd, div.body li { line-height: 1.4em; } div.admonition { background: #fafafa; margin: 20px -30px; padding: 10px 30px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.admonition p.admonition-title { font-family: {{ headline_font_stack }}; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight{ background-color: white; } dt:target, .highlight { background: #FAF3E8; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } div.warning { background-color: #ffe4e4; border: 1px solid #f66; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt { font-family: {{ code_font_stack }}; font-size: 0.9em; } img.screenshot { } tt.descname, tt.descclassname { font-size: 0.95em; } tt.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #eee; background: #fdfdfd; font-size: 0.9em; } table.footnote + table.footnote { margin-top: -15px; border-top: none; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.footnote td.label { width: 0px; padding: 0.3em 0 0.3em 0.5em; } table.footnote td { padding: 0.3em 0.5em; } dl { margin: 0; padding: 0; } dl dd { margin-left: 30px; } blockquote { margin: 0 0 0 30px; padding: 0; } ul { margin: 10px 0 10px 30px; padding: 0; } pre { background: #F0FFEB; padding: 7px 10px; margin: 15px 0; border: 1px solid #C7ECB8; border-radius: 2px; -moz-border-radius: 2px; -webkit-border-radius: 2px; line-height: 1.3em; } tt { background: #F0FFEB; color: #222; /* padding: 1px 2px; */ } tt.xref, a tt { background: #F0FFEB; border-bottom: 1px solid white; } a.reference { text-decoration: none; border-bottom: 1px dashed #DCF0D5; } a.reference:hover { border-bottom: 1px solid #6D4100; } a.footnote-reference { text-decoration: none; font-size: 0.7em; vertical-align: top; border-bottom: 1px dashed #DCF0D5; } a.footnote-reference:hover { border-bottom: 1px solid #6D4100; } a:hover tt { background: #EEE; } amqp-1.4.9/docs/_theme/celery/theme.conf0000644000076500000000000000007312270020623020514 0ustar asksolwheel00000000000000[theme] inherit = basic stylesheet = celery.css [options] amqp-1.4.9/docs/changelog.rst0000644000076500000000000000000012644064263021721 1amqp-1.4.9/Changelogustar asksolwheel00000000000000amqp-1.4.9/docs/conf.py0000644000076500000000000000707712644026054015342 0ustar asksolwheel00000000000000# -*- coding: utf-8 -*- import sys import os this = os.path.dirname(os.path.abspath(__file__)) # If your extensions are in another directory, add it here. If the directory # is relative to the documentation root, use os.path.abspath to make it # absolute, like shown here. sys.path.append(os.path.join(os.pardir, "tests")) sys.path.append(os.path.join(this, "_ext")) import amqp # General configuration # --------------------- extensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.intersphinx', 'sphinxcontrib.issuetracker'] html_show_sphinx = False # Add any paths that contain templates here, relative to this directory. templates_path = ['.templates'] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'py-amqp' copyright = u'2009-2012, Ask Solem & Contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = ".".join(map(str, amqp.VERSION[0:2])) # The full version, including alpha/beta/rc tags. release = amqp.__version__ exclude_trees = ['.build'] # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True intersphinx_mapping = { } # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'trac' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['.static'] html_use_smartypants = True # If false, no module index is generated. html_use_modindex = True # If false, no index is generated. html_use_index = True latex_documents = [ ('index', 'py-amqp.tex', ur'py-amqp Documentation', ur'Ask Solem & Contributors', 'manual'), ] html_theme = "celery" html_theme_path = ["_theme"] html_sidebars = { 'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'], '**': ['sidebarlogo.html', 'relations.html', 'sourcelink.html', 'searchbox.html'], } ### Issuetracker if False: issuetracker = "github" issuetracker_project = "celery/py-amqp" issuetracker_issue_pattern = r'[Ii]ssue #(\d+)' # -- Options for Epub output ------------------------------------------------ # Bibliographic Dublin Core info. epub_title = 'py-amqp Manual, Version 1.0' epub_author = 'Ask Solem' epub_publisher = 'Celery Project' epub_copyright = '2009-2012' # The language of the text. It defaults to the language option # or en if the language is not set. epub_language = 'en' # The scheme of the identifier. Typical schemes are ISBN or URL. epub_scheme = 'ISBN' # The unique identifier of the text. This can be a ISBN number # or the project homepage. epub_identifier = 'celeryproject.org' # A unique identification for the text. epub_uid = 'py-amqp Manual, Version 1.0' # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_pre_files = [] # HTML files shat should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_post_files = [] # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html'] # The depth of the table of contents in toc.ncx. epub_tocdepth = 3 amqp-1.4.9/docs/includes/0000755000076500000000000000000012644064471015643 5ustar asksolwheel00000000000000amqp-1.4.9/docs/includes/intro.txt0000644000076500000000000000720312275713667017551 0ustar asksolwheel00000000000000:Version: 1.4.2 :Web: http://amqp.readthedocs.org/ :Download: http://pypi.python.org/pypi/amqp/ :Source: http://github.com/celery/py-amqp/ :Keywords: amqp, rabbitmq About ===== This is a fork of amqplib_ which was originally written by Barry Pederson. It is maintained by the Celery_ project, and used by `kombu`_ as a pure python alternative when `librabbitmq`_ is not available. This library should be API compatible with `librabbitmq`_. .. _amqplib: http://pypi.python.org/pypi/amqplib .. _Celery: http://celeryproject.org/ .. _kombu: http://kombu.readthedocs.org/ .. _librabbitmq: http://pypi.python.org/pypi/librabbitmq Differences from `amqplib`_ =========================== - Supports draining events from multiple channels (``Connection.drain_events``) - Support for timeouts - Channels are restored after channel error, instead of having to close the connection. - Support for heartbeats - ``Connection.heartbeat_tick(rate=2)`` must called at regular intervals (half of the heartbeat value if rate is 2). - Or some other scheme by using ``Connection.send_heartbeat``. - Supports RabbitMQ extensions: - Consumer Cancel Notifications - by default a cancel results in ``ChannelError`` being raised - but not if a ``on_cancel`` callback is passed to ``basic_consume``. - Publisher confirms - ``Channel.confirm_select()`` enables publisher confirms. - ``Channel.events['basic_ack'].append(my_callback)`` adds a callback to be called when a message is confirmed. This callback is then called with the signature ``(delivery_tag, multiple)``. - Exchange-to-exchange bindings: ``exchange_bind`` / ``exchange_unbind``. - ``Channel.confirm_select()`` enables publisher confirms. - ``Channel.events['basic_ack'].append(my_callback)`` adds a callback to be called when a message is confirmed. This callback is then called with the signature ``(delivery_tag, multiple)``. - Support for ``basic_return`` - Uses AMQP 0-9-1 instead of 0-8. - ``Channel.access_request`` and ``ticket`` arguments to methods **removed**. - Supports the ``arguments`` argument to ``basic_consume``. - ``internal`` argument to ``exchange_declare`` removed. - ``auto_delete`` argument to ``exchange_declare`` deprecated - ``insist`` argument to ``Connection`` removed. - ``Channel.alerts`` has been removed. - Support for ``Channel.basic_recover_async``. - ``Channel.basic_recover`` deprecated. - Exceptions renamed to have idiomatic names: - ``AMQPException`` -> ``AMQPError`` - ``AMQPConnectionException`` -> ConnectionError`` - ``AMQPChannelException`` -> ChannelError`` - ``Connection.known_hosts`` removed. - ``Connection`` no longer supports redirects. - ``exchange`` argument to ``queue_bind`` can now be empty to use the "default exchange". - Adds ``Connection.is_alive`` that tries to detect whether the connection can still be used. - Adds ``Connection.connection_errors`` and ``.channel_errors``, a list of recoverable errors. - Exposes the underlying socket as ``Connection.sock``. - Adds ``Channel.no_ack_consumers`` to keep track of consumer tags that set the no_ack flag. - Slightly better at error recovery Further ======= - Differences between AMQP 0.8 and 0.9.1 http://www.rabbitmq.com/amqp-0-8-to-0-9-1.html - AMQP 0.9.1 Quick Reference http://www.rabbitmq.com/amqp-0-9-1-quickref.html - RabbitMQ Extensions http://www.rabbitmq.com/extensions.html - For more information about AMQP, visit http://www.amqp.org - For other Python client libraries see: http://www.rabbitmq.com/devtools.html#python-dev amqp-1.4.9/docs/index.rst0000644000076500000000000000053412270020623015662 0ustar asksolwheel00000000000000============================================= amqp - Python AMQP low-level client library ============================================= .. include:: includes/intro.txt Contents ======== .. toctree:: :maxdepth: 2 reference/index changelog Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` amqp-1.4.9/docs/Makefile0000644000076500000000000000472112270020623015463 0ustar asksolwheel00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d .build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html web pickle htmlhelp latex changes linkcheck help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview over all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" clean: -rm -rf .build/* html: mkdir -p .build/html .build/doctrees $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) .build/html @echo @echo "Build finished. The HTML pages are in .build/html." coverage: mkdir -p .build/coverage .build/doctrees $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) .build/coverage @echo @echo "Build finished." pickle: mkdir -p .build/pickle .build/doctrees $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) .build/pickle @echo @echo "Build finished; now you can process the pickle files." web: pickle json: mkdir -p .build/json .build/doctrees $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) .build/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: mkdir -p .build/htmlhelp .build/doctrees $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) .build/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in .build/htmlhelp." latex: mkdir -p .build/latex .build/doctrees $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) .build/latex @echo @echo "Build finished; the LaTeX files are in .build/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: mkdir -p .build/changes .build/doctrees $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) .build/changes @echo @echo "The overview file is in .build/changes." linkcheck: mkdir -p .build/linkcheck .build/doctrees $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) .build/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in .build/linkcheck/output.txt." amqp-1.4.9/docs/reference/0000755000076500000000000000000012644064471015773 5ustar asksolwheel00000000000000amqp-1.4.9/docs/reference/amqp.abstract_channel.rst0000644000076500000000000000042012270020623022733 0ustar asksolwheel00000000000000===================================================== amqp.abstract_channel ===================================================== .. contents:: :local: .. currentmodule:: amqp.abstract_channel .. automodule:: amqp.abstract_channel :members: :undoc-members: amqp-1.4.9/docs/reference/amqp.basic_message.rst0000644000076500000000000000040712270020623022232 0ustar asksolwheel00000000000000===================================================== amqp.basic_message ===================================================== .. contents:: :local: .. currentmodule:: amqp.basic_message .. automodule:: amqp.basic_message :members: :undoc-members: amqp-1.4.9/docs/reference/amqp.channel.rst0000644000076500000000000000036512270020623021060 0ustar asksolwheel00000000000000===================================================== amqp.channel ===================================================== .. contents:: :local: .. currentmodule:: amqp.channel .. automodule:: amqp.channel :members: :undoc-members: amqp-1.4.9/docs/reference/amqp.connection.rst0000644000076500000000000000037612270020623021611 0ustar asksolwheel00000000000000===================================================== amqp.connection ===================================================== .. contents:: :local: .. currentmodule:: amqp.connection .. automodule:: amqp.connection :members: :undoc-members: amqp-1.4.9/docs/reference/amqp.exceptions.rst0000644000076500000000000000037612270020623021633 0ustar asksolwheel00000000000000===================================================== amqp.exceptions ===================================================== .. contents:: :local: .. currentmodule:: amqp.exceptions .. automodule:: amqp.exceptions :members: :undoc-members: amqp-1.4.9/docs/reference/amqp.five.rst0000644000076500000000000000035412270020623020377 0ustar asksolwheel00000000000000===================================================== amqp.five ===================================================== .. contents:: :local: .. currentmodule:: amqp.five .. automodule:: amqp.five :members: :undoc-members: amqp-1.4.9/docs/reference/amqp.method_framing.rst0000644000076500000000000000041212270020623022424 0ustar asksolwheel00000000000000===================================================== amqp.method_framing ===================================================== .. contents:: :local: .. currentmodule:: amqp.method_framing .. automodule:: amqp.method_framing :members: :undoc-members: amqp-1.4.9/docs/reference/amqp.protocol.rst0000644000076500000000000000037012270020623021305 0ustar asksolwheel00000000000000===================================================== amqp.protocol ===================================================== .. contents:: :local: .. currentmodule:: amqp.protocol .. automodule:: amqp.protocol :members: :undoc-members: amqp-1.4.9/docs/reference/amqp.serialization.rst0000644000076500000000000000040712270020623022322 0ustar asksolwheel00000000000000===================================================== amqp.serialization ===================================================== .. contents:: :local: .. currentmodule:: amqp.serialization .. automodule:: amqp.serialization :members: :undoc-members: amqp-1.4.9/docs/reference/amqp.transport.rst0000644000076500000000000000037312270020623021503 0ustar asksolwheel00000000000000===================================================== amqp.transport ===================================================== .. contents:: :local: .. currentmodule:: amqp.transport .. automodule:: amqp.transport :members: :undoc-members: amqp-1.4.9/docs/reference/amqp.utils.rst0000644000076500000000000000035712270020623020611 0ustar asksolwheel00000000000000===================================================== amqp.utils ===================================================== .. contents:: :local: .. currentmodule:: amqp.utils .. automodule:: amqp.utils :members: :undoc-members: amqp-1.4.9/docs/reference/index.rst0000644000076500000000000000053312270020623017617 0ustar asksolwheel00000000000000.. _apiref: =============== API Reference =============== :Release: |version| :Date: |today| .. toctree:: :maxdepth: 1 amqp.connection amqp.channel amqp.basic_message amqp.exceptions amqp.abstract_channel amqp.transport amqp.method_framing amqp.protocol amqp.serialization amqp.utils amqp.five amqp-1.4.9/docs/templates/0000755000076500000000000000000012644064471016033 5ustar asksolwheel00000000000000amqp-1.4.9/docs/templates/readme.txt0000644000076500000000000000032212270020623020010 0ustar asksolwheel00000000000000===================================================================== Python AMQP 0.9.1 client library ===================================================================== .. include:: ../includes/intro.txt amqp-1.4.9/extra/0000755000076500000000000000000012644064471014230 5ustar asksolwheel00000000000000amqp-1.4.9/extra/generate_skeleton_0_8.py0000755000076500000000000003046212644026054020751 0ustar asksolwheel00000000000000#!/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 xml.etree import ElementTree ######### # # Helper code inspired by # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/498286 # described in http://www.agapow.net/programming/python/the-etree-tail-quirk # def _textlist(self, _addtail=False): '''Returns a list of text strings contained within an element and its sub-elements. Helpful for extracting text from prose-oriented XML (such as XHTML or DocBook). ''' result = [] if (not _addtail) and (self.text is not None): result.append(self.text) for elem in self: result.extend(elem.textlist(True)) if _addtail and self.tail is not None: result.append(self.tail) return result # inject the new method into the ElementTree framework ElementTree._Element.textlist = _textlist # # ######### domains = {} method_name_map = {} def _fixup_method_name(class_element, method_element): if class_element.attrib['name'] != class_element.attrib['handler']: prefix = '%s_' % class_element.attrib['name'] else: prefix = '' return ('%s%s' % (prefix, method_element.attrib['name'])).replace('-', '_') def _fixup_field_name(field_element): result = field_element.attrib['name'].replace(' ', '_') if result == 'global': result = 'a_global' return result def _field_type(field_element): if 'type' in field_element.attrib: return field_element.attrib['type'] if 'domain' in field_element.attrib: return domains[field_element.attrib['domain']] def _reindent(s, indent, reformat=True): """ Remove the existing indentation from each line of a chunk of text, s, and then prefix each line with a new indent string. Also removes trailing whitespace from each line, and leading and trailing blank lines. """ s = textwrap.dedent(s) s = s.split('\n') s = [x.rstrip() for x in s] while s and (not s[0]): s = s[1:] while s and (not s[-1]): s = s[:-1] if reformat: s = '\n'.join(s) s = textwrap.wrap(s, initial_indent=indent, subsequent_indent=indent) else: s = [indent + x for x in s] return '\n'.join(s) + '\n' def generate_docstr(element, indent='', wrap=None): print 'Generate objects' """ Generate a Python docstr for a given element in the AMQP XML spec file. The element could be a class or method The 'wrap' parameter is an optional chunk of text that's added to the beginning and end of the resulting docstring. """ result = [] txt = element.text and element.text.rstrip() if txt: result.append(_reindent(txt, indent)) result.append(indent) extra_indent = '' """ rules = element.findall('rule') if rules: result.append(indent + 'RULES:') for r in rules: result.append(indent + 'RULE:') result.append(indent) extra_indent = ' ' rule_docs = generate_docstr(r, indent + ' ') if rule_docs: result.append(extra_indent) result.append(rule_docs) result.append(indent) """ for d in element.findall('doc') + element.findall('rule'): if d.tag == 'rule': result.append(indent + 'RULE:') result.append(indent) extra_indent = ' ' d = d.findall('doc')[0] docval = ''.join(d.textlist()).rstrip() if not docval: continue reformat = True if 'name' in d.attrib: result.append(indent + d.attrib['name'].upper() + ':') result.append(indent) extra_indent = ' ' if d.attrib['name'] == 'grammar': reformat = False # Don't want re-indenting to mess this up #else: # extra_indent = '' result.append(_reindent(docval, indent + extra_indent, reformat)) result.append(indent) fields = element.findall('field') if fields: result.append(indent + 'PARAMETERS:') for f in fields: result.append(indent + ' ' + _fixup_field_name(f) + ': ' + _field_type(f)) field_docs = generate_docstr(f, indent + ' ') if field_docs: result.append(indent) result.append(field_docs) result.append(indent) if not result: return None if wrap is not None: result = [wrap] + result + [wrap] return '\n'.join(x.rstrip() for x in result) + '\n' def generate_methods(class_element, out): methods = class_element.findall('method') methods.sort(key=lambda x: x.attrib['name']) for amqp_method in methods: fields = amqp_method.findall('field') fieldnames = [_fixup_field_name(x) for x in fields] # move any 'ticket' arguments to the end of the method declaration # so that they can have a default value. if 'ticket' in fieldnames: fieldnames = [x for x in fieldnames if x != 'ticket'] + ['ticket'] chassis = [x.attrib['name'] for x in amqp_method.findall('chassis')] if 'server' in chassis: params = ['self'] if 'content' in amqp_method.attrib: params.append('msg') out.write(' def %s(%s):\n' % ( _fixup_method_name(class_element, amqp_method), ', '.join(params + fieldnames)), ) s = generate_docstr(amqp_method, ' ', ' """') if s: out.write(s) if fields: out.write(' args = AMQPWriter()\n') smf_arg = ', args' else: smf_arg = '' for f in fields: out.write(' args.write_%s(%s)\n' % ( _field_type(f), _fixup_field_name(f))) if class_element.attrib['name'] == 'connection': smf_pattern = ' self.send_method_frame(0, (%s, %s)%s)\n' else: smf_pattern = ' self.send_method_frame((%s, %s)%s)\n' out.write(smf_pattern % (class_element.attrib['index'], amqp_method.attrib['index'], smf_arg)) if 'synchronous' in amqp_method.attrib: responses = [x.attrib['name'] for x in amqp_method.findall('response')] out.write(' return self.wait(allowed_methods=[\n') for r in responses: resp = method_name_map[(class_element.attrib['name'], r)] out.write( ' (%s, %s), # %s\n' % resp) out.write(' ])\n') out.write('\n\n') if 'client' in chassis: out.write(' def _%s(self, args):\n' % ( _fixup_method_name(class_element, amqp_method), )) s = generate_docstr(amqp_method, ' ', ' """') if s: out.write(s) need_pass = True for f in fields: out.write(' %s = args.read_%s()\n' % ( _fixup_field_name(f), _field_type(f))) need_pass = False if 'content' in amqp_method.attrib: out.write(' msg = self.wait()\n') need_pass = False if need_pass: out.write(' pass\n') out.write('\n\n') def generate_class(spec, class_element, out): out.write('class %s(object):\n' % ( class_element.attrib['name'].capitalize(), )) s = generate_docstr(class_element, ' ', ' """') if s: out.write(s) generate_methods(class_element, out) # # Generate methods for handled classes # for amqp_class in spec.findall('class'): if (amqp_class.attrib['handler'] == class_element.attrib['name']) and \ (amqp_class.attrib['name'] != class_element.attrib['name']): out.write(' #############\n') out.write(' #\n') out.write(' # %s\n' % amqp_class.attrib['name'].capitalize()) out.write(' #\n') s = generate_docstr(amqp_class, ' # ', ' # ') if s: out.write(s) out.write('\n') generate_methods(amqp_class, out) def generate_module(spec, out): """ Given an AMQP spec parsed into an xml.etree.ElemenTree, and a file-like 'out' object to write to, generate the skeleton of a Python module. """ # # HACK THE SPEC so that 'access' is handled by # 'channel' instead of 'connection' # for amqp_class in spec.findall('class'): if amqp_class.attrib['name'] == 'access': amqp_class.attrib['handler'] = 'channel' # # Build up some helper dictionaries # for domain in spec.findall('domain'): domains[domain.attrib['name']] = domain.attrib['type'] for amqp_class in spec.findall('class'): for amqp_method in amqp_class.findall('method'): method_name_map[(amqp_class.attrib['name'], amqp_method.attrib['name'])] = \ ( amqp_class.attrib['index'], amqp_method.attrib['index'], (amqp_class.attrib['handler'].capitalize() + '.' + _fixup_method_name(amqp_class, amqp_method)), ) #### Actually generate output for amqp_class in spec.findall('class'): if amqp_class.attrib['handler'] == amqp_class.attrib['name']: generate_class(spec, amqp_class, out) out.write('_METHOD_MAP = {\n') for amqp_class in spec.findall('class'): print amqp_class.attrib # for chassis in amqp_class.findall('chassis'): # print ' ', chassis.attrib for amqp_method in amqp_class.findall('method'): # print ' ', amqp_method.attrib # for chassis in amqp_method.findall('chassis'): # print ' ', chassis.attrib chassis = [x.attrib['name'] for x in amqp_method.findall('chassis')] if 'client' in chassis: out.write(" (%s, %s): (%s, %s._%s),\n" % ( amqp_class.attrib['index'], amqp_method.attrib['index'], amqp_class.attrib['handler'].capitalize(), amqp_class.attrib['handler'].capitalize(), _fixup_method_name(amqp_class, amqp_method))) out.write('}\n\n') out.write('_METHOD_NAME_MAP = {\n') for amqp_class in spec.findall('class'): for amqp_method in amqp_class.findall('method'): out.write(" (%s, %s): '%s.%s',\n" % ( amqp_class.attrib['index'], amqp_method.attrib['index'], amqp_class.attrib['handler'].capitalize(), _fixup_method_name(amqp_class, amqp_method))) out.write('}\n') def main(argv=None): if argv is None: argv = sys.argv if len(argv) < 2: print('Usage: %s []' % 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()) amqp-1.4.9/extra/README0000644000076500000000000000047612270020623015101 0ustar asksolwheel00000000000000generate_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 amqp-1.4.9/extra/release/0000755000076500000000000000000012644064471015650 5ustar asksolwheel00000000000000amqp-1.4.9/extra/release/bump_version.py0000755000076500000000000001137212270020623020723 0ustar asksolwheel00000000000000#!/usr/bin/env python from __future__ import absolute_import import errno import os import re import shlex import subprocess import sys from contextlib import contextmanager from tempfile import NamedTemporaryFile rq = lambda s: s.strip("\"'") def cmd(*args): return subprocess.Popen(args, stdout=subprocess.PIPE).communicate()[0] @contextmanager def no_enoent(): try: yield except OSError as exc: if exc.errno != errno.ENOENT: raise class StringVersion(object): def decode(self, s): s = rq(s) text = "" major, minor, release = s.split(".") if not release.isdigit(): pos = release.index(re.split("\d+", release)[1][0]) release, text = release[:pos], release[pos:] return int(major), int(minor), int(release), text def encode(self, v): return ".".join(map(str, v[:3])) + v[3] to_str = StringVersion().encode from_str = StringVersion().decode class TupleVersion(object): def decode(self, s): v = list(map(rq, s.split(", "))) return (tuple(map(int, v[0:3])) + tuple(["".join(v[3:])])) def encode(self, v): v = list(v) def quote(lit): if isinstance(lit, basestring): return '"{0}"'.format(lit) return str(lit) if not v[-1]: v.pop() return ", ".join(map(quote, v)) class VersionFile(object): def __init__(self, filename): self.filename = filename self._kept = None def _as_orig(self, version): return self.wb.format(version=self.type.encode(version), kept=self._kept) def write(self, version): pattern = self.regex with no_enoent(): with NamedTemporaryFile() as dest: with open(self.filename) as orig: for line in orig: if pattern.match(line): dest.write(self._as_orig(version)) else: dest.write(line) os.rename(dest.name, self.filename) def parse(self): pattern = self.regex gpos = 0 with open(self.filename) as fh: for line in fh: m = pattern.match(line) if m: if "?P" in pattern.pattern: self._kept, gpos = m.groupdict()["keep"], 1 return self.type.decode(m.groups()[gpos]) class PyVersion(VersionFile): regex = re.compile(r'^VERSION\s*=\s*\((.+?)\)') wb = "VERSION = ({version})\n" type = TupleVersion() class SphinxVersion(VersionFile): regex = re.compile(r'^:[Vv]ersion:\s*(.+?)$') wb = ':Version: {version}\n' type = StringVersion() class CPPVersion(VersionFile): regex = re.compile(r'^\#\s*define\s*(?P\w*)VERSION\s+(.+)') wb = '#define {kept}VERSION "{version}"\n' type = StringVersion() _filetype_to_type = {"py": PyVersion, "rst": SphinxVersion, "txt": SphinxVersion, "c": CPPVersion, "h": CPPVersion} def filetype_to_type(filename): _, _, suffix = filename.rpartition(".") return _filetype_to_type[suffix](filename) def bump(*files, **kwargs): version = kwargs.get("version") before_commit = kwargs.get("before_commit") files = [filetype_to_type(f) for f in files] versions = [v.parse() for v in files] current = list(reversed(sorted(versions)))[0] # find highest current = current.split()[0] # only first sentence if version: next = from_str(version) else: major, minor, release, text = current if text: raise Exception("Can't bump alpha releases") next = (major, minor, release + 1, text) print("Bump version from {0} -> {1}".format(to_str(current), to_str(next))) for v in files: print(" writing {0.filename!r}...".format(v)) v.write(next) if before_commit: cmd(*shlex.split(before_commit)) print(cmd("git", "commit", "-m", "Bumps version to {0}".format( to_str(next)), *[f.filename for f in files])) print(cmd("git", "tag", "v{0}".format(to_str(next)))) def main(argv=sys.argv, version=None, before_commit=None): if not len(argv) > 1: print("Usage: distdir [docfile] -- ") sys.exit(0) args = [] for arg in argv: if arg.startswith("--before-commit="): _, before_commit = arg.split('=') else: args.append(arg) if "--" in args: c = args.index('--') version = args[c + 1] argv = args[:c] bump(*args[1:], version=version, before_commit=before_commit) if __name__ == "__main__": main() amqp-1.4.9/extra/release/sphinx-to-rst.py0000755000076500000000000000355412270020623020755 0ustar asksolwheel00000000000000#!/usr/bin/env python import os import re import sys dirname = "" RE_CODE_BLOCK = re.compile(r'.. code-block:: (.+?)\s*$') RE_INCLUDE = re.compile(r'.. include:: (.+?)\s*$') RE_REFERENCE = re.compile(r':(.+?):`(.+?)`') def include_file(lines, pos, match): global dirname orig_filename = match.groups()[0] filename = os.path.join(dirname, orig_filename) fh = open(filename) try: old_dirname = dirname dirname = os.path.dirname(orig_filename) try: lines[pos] = sphinx_to_rst(fh) finally: dirname = old_dirname finally: fh.close() def replace_code_block(lines, pos, match): lines[pos] = "" curpos = pos - 1 # Find the first previous line with text to append "::" to it. while True: prev_line = lines[curpos] if not prev_line.isspace(): prev_line_with_text = curpos break curpos -= 1 if lines[prev_line_with_text].endswith(":"): lines[prev_line_with_text] += ":" else: lines[prev_line_with_text] += "::" TO_RST_MAP = {RE_CODE_BLOCK: replace_code_block, RE_REFERENCE: r'``\2``', RE_INCLUDE: include_file} def _process(lines): lines = list(lines) # non-destructive for i, line in enumerate(lines): for regex, alt in TO_RST_MAP.items(): if callable(alt): match = regex.match(line) if match: alt(lines, i, match) line = lines[i] else: lines[i] = regex.sub(alt, line) return lines def sphinx_to_rst(fh): return "".join(_process(fh)) if __name__ == "__main__": global dirname dirname = os.path.dirname(sys.argv[1]) fh = open(sys.argv[1]) try: print(sphinx_to_rst(fh)) finally: fh.close() amqp-1.4.9/extra/update_comments_from_spec.py0000644000076500000000000000354512644026054022030 0ustar asksolwheel00000000000000from __future__ import absolute_import import os import sys import re default_source_file = os.path.join( os.path.dirname(__file__), '../amqp/channel.py', ) RE_COMMENTS = re.compile( '(?Pdef\s+(?P[a-zA-Z0-9_]+)\(.*?\)' ':\n+\s+""")(?P.*?)(?=""")', re.MULTILINE | re.DOTALL ) USAGE = """\ Usage: %s []\ """ def update_comments(comments_file, impl_file, result_file): text_file = open(impl_file, 'r') source = text_file.read() comments = get_comments(comments_file) for def_name, comment in comments.items(): source = replace_comment_per_def( source, result_file, def_name, comment ) new_file = open(result_file, 'w+') new_file.write(source) def get_comments(filename): text_file = open(filename, 'r') whole_source = text_file.read() comments = {} all_matches = RE_COMMENTS.finditer(whole_source) for match in all_matches: comments[match.group('mname')] = match.group('comment') #print('method: %s \ncomment: %s' % ( # match.group('mname'), match.group('comment'))) return comments def replace_comment_per_def(source, result_file, def_name, new_comment): regex = ('(?Pdef\s+' + def_name + '\(.*?\):\n+\s+""".*?\n).*?(?=""")') #print('method and comment:' + def_name + new_comment) result = re.sub(regex, '\g' + new_comment, source, 0, re.MULTILINE | re.DOTALL) return result def main(argv=None): if argv is None: argv = sys.argv if len(argv) < 3: print(USAGE % argv[0]) return 1 impl_file = default_source_file if len(argv) >= 4: impl_file = argv[3] update_comments(argv[1], impl_file, argv[2]) if __name__ == '__main__': sys.exit(main()) amqp-1.4.9/funtests/0000755000076500000000000000000012644064471014760 5ustar asksolwheel00000000000000amqp-1.4.9/funtests/run_all.py0000755000076500000000000000227612270020623016762 0ustar asksolwheel00000000000000#!/usr/bin/env python """Run all the unittest modules 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 import unittest import settings TEST_NAMES = [ 'test_exceptions', 'test_serialization', 'test_basic_message', 'test_connection', 'test_channel', 'test_with', ] def main(): suite = unittest.TestLoader().loadTestsFromNames(TEST_NAMES) unittest.TextTestRunner(**settings.test_args).run(suite) if __name__ == '__main__': main() amqp-1.4.9/funtests/settings.py0000644000076500000000000000546612270020623017167 0ustar asksolwheel00000000000000"""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( '--port', dest='port', help='port for the broker', default=5672, ) parser.add_option( '-v', '--verbosity', dest='verbose', action='store_true', help='Run unittests with increased verbosity', default=False, ) options, args = parser.parse_args() if options.debug: console = logging.StreamHandler() console.setLevel(logging.DEBUG) formatter = logging.Formatter( '%(name)-12s: %(levelname)-8s %(message)s', ) console.setFormatter(formatter) amqp_logger = logging.getLogger('amqp') amqp_logger.addHandler(console) amqp_logger.setLevel(logging.DEBUG) connect_args['host'] = options.host connect_args['userid'] = options.userid connect_args['password'] = options.password connect_args['ssl'] = options.ssl if options.verbose: test_args['verbosity'] = 2 parse_args() amqp-1.4.9/funtests/test_basic_message.py0000755000076500000000000000733312270020623021151 0ustar asksolwheel00000000000000#!/usr/bin/env python """ Test the amqp.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 ImportError: import pickle # noqa import settings from amqp.basic_message import Message class TestBasicMessage(unittest.TestCase): def check_proplist(self, msg): """Check roundtrip processing of a single object""" raw_properties = msg._serialize_properties() new_msg = Message() new_msg._load_properties(raw_properties) new_msg.body = msg.body self.assertEqual(msg, new_msg) def test_eq(self): msg = Message('hello', content_type='text/plain') self.assertNotEqual(msg, None) # # Make sure that something that looks vaguely # like a Message doesn't raise an Attribute # error when compared to a Message, and instead # returns False # class FakeMsg(object): pass fake_msg = FakeMsg() fake_msg.properties = {'content_type': 'text/plain'} self.assertNotEqual(msg, fake_msg) def test_pickle(self): msg = Message( 'some body' * 200000, content_type='text/plain', content_encoding='utf-8', application_headers={ 'foo': 7, 'bar': 'baz', 'd2': {'foo2': 'xxx', 'foo3': -1}, }, delivery_mode=1, priority=7, ) msg2 = pickle.loads(pickle.dumps(msg)) self.assertEqual(msg, msg2) def test_roundtrip(self): """Check round-trip processing of content-properties.""" self.check_proplist(Message()) self.check_proplist(Message(content_type='text/plain')) self.check_proplist(Message( content_type='text/plain', content_encoding='utf-8', application_headers={ 'foo': 7, 'bar': 'baz', 'd2': {'foo2': 'xxx', 'foo3': -1}, }, delivery_mode=1, priority=7, )) self.check_proplist(Message( application_headers={ 'regular': datetime(2007, 11, 12, 12, 34, 56), 'dst': datetime(2007, 7, 12, 12, 34, 56), }, )) n = datetime.now() # AMQP only does timestamps to 1-second resolution n = n.replace(microsecond=0) self.check_proplist(Message( application_headers={'foo': n}), ) self.check_proplist(Message( application_headers={'foo': Decimal('10.1')}), ) self.check_proplist(Message( application_headers={'foo': Decimal('-1987654.193')}), ) self.check_proplist(Message( timestamp=datetime(1980, 1, 2, 3, 4, 6)), ) def main(): suite = unittest.TestLoader().loadTestsFromTestCase(TestBasicMessage) unittest.TextTestRunner(**settings.test_args).run(suite) if __name__ == '__main__': main() amqp-1.4.9/funtests/test_channel.py0000755000076500000000000002650512617231407020007 0ustar asksolwheel00000000000000#!/usr/bin/env python """ Test amqp.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 unittest import settings from amqp import ChannelError, Connection, Message, FrameSyntaxError class TestChannel(unittest.TestCase): def setUp(self): self.conn = Connection(**settings.connect_args) self.ch = self.conn.channel() def tearDown(self): self.ch.close() self.conn.close() def test_defaults(self): """Test how a queue defaults to being bound to an AMQP default exchange, and how publishing defaults to the default exchange, and basic_get defaults to getting from the most recently declared queue, and queue_delete defaults to deleting the most recently declared queue.""" msg = Message( 'funtest message', content_type='text/plain', application_headers={'foo': 7, 'bar': 'baz'}, ) qname, _, _ = self.ch.queue_declare() self.ch.basic_publish(msg, routing_key=qname) msg2 = self.ch.basic_get(no_ack=True) self.assertEqual(msg, msg2) n = self.ch.queue_purge() self.assertEqual(n, 0) n = self.ch.queue_delete() self.assertEqual(n, 0) def test_encoding(self): my_routing_key = 'funtest.test_queue' qname, _, _ = self.ch.queue_declare() self.ch.queue_bind(qname, 'amq.direct', routing_key=my_routing_key) # # No encoding, body passed through unchanged # msg = Message('hello world') self.ch.basic_publish(msg, 'amq.direct', routing_key=my_routing_key) msg2 = self.ch.basic_get(qname, no_ack=True) if sys.version_info[0] < 3: self.assertFalse(hasattr(msg2, 'content_encoding')) self.assertTrue(isinstance(msg2.body, str)) self.assertEqual(msg2.body, 'hello world') # # Default UTF-8 encoding of unicode body, returned as unicode # msg = Message(u'hello world') self.ch.basic_publish(msg, 'amq.direct', routing_key=my_routing_key) msg2 = self.ch.basic_get(qname, no_ack=True) self.assertEqual(msg2.content_encoding, 'UTF-8') self.assertTrue(isinstance(msg2.body, unicode)) self.assertEqual(msg2.body, u'hello world') # # Explicit latin_1 encoding, still comes back as unicode # msg = Message(u'hello world', content_encoding='latin_1') self.ch.basic_publish(msg, 'amq.direct', routing_key=my_routing_key) msg2 = self.ch.basic_get(qname, no_ack=True) self.assertEqual(msg2.content_encoding, 'latin_1') self.assertTrue(isinstance(msg2.body, unicode)) self.assertEqual(msg2.body, u'hello world') # # Plain string with specified encoding comes back as unicode # msg = Message('hello w\xf6rld', content_encoding='latin_1') self.ch.basic_publish(msg, 'amq.direct', routing_key=my_routing_key) msg2 = self.ch.basic_get(qname, no_ack=True) self.assertEqual(msg2.content_encoding, 'latin_1') self.assertTrue(isinstance(msg2.body, unicode)) self.assertEqual(msg2.body, u'hello w\u00f6rld') # # Plain string (bytes in Python 3.x) with bogus encoding # # don't really care about latin_1, just want bytes test_bytes = u'hello w\xd6rld'.encode('latin_1') msg = Message(test_bytes, content_encoding='I made this up') self.ch.basic_publish(msg, 'amq.direct', routing_key=my_routing_key) msg2 = self.ch.basic_get(qname, no_ack=True) self.assertEqual(msg2.content_encoding, 'I made this up') self.assertTrue(isinstance(msg2.body, bytes)) self.assertEqual(msg2.body, test_bytes) # # Turn off auto_decode for remaining tests # self.ch.auto_decode = False # # Unicode body comes back as utf-8 encoded str # msg = Message(u'hello w\u00f6rld') self.ch.basic_publish(msg, 'amq.direct', routing_key=my_routing_key) msg2 = self.ch.basic_get(qname, no_ack=True) self.assertEqual(msg2.content_encoding, 'UTF-8') self.assertTrue(isinstance(msg2.body, bytes)) self.assertEqual(msg2.body, u'hello w\xc3\xb6rld'.encode('latin_1')) # # Plain string with specified encoding stays plain string # msg = Message('hello w\xf6rld', content_encoding='latin_1') self.ch.basic_publish(msg, 'amq.direct', routing_key=my_routing_key) msg2 = self.ch.basic_get(qname, no_ack=True) self.assertEqual(msg2.content_encoding, 'latin_1') self.assertTrue(isinstance(msg2.body, bytes)) self.assertEqual(msg2.body, u'hello w\xf6rld'.encode('latin_1')) # # Explicit latin_1 encoding, comes back as str # msg = Message(u'hello w\u00f6rld', content_encoding='latin_1') self.ch.basic_publish(msg, 'amq.direct', routing_key=my_routing_key) msg2 = self.ch.basic_get(qname, no_ack=True) self.assertEqual(msg2.content_encoding, 'latin_1') self.assertTrue(isinstance(msg2.body, bytes)) self.assertEqual(msg2.body, u'hello w\xf6rld'.encode('latin_1')) def test_queue_delete_empty(self): self.assertFalse( self.ch.queue_delete('bogus_queue_that_does_not_exist') ) def test_survives_channel_error(self): with self.assertRaises(ChannelError): self.ch.queue_declare('krjqheewq_bogus', passive=True) self.ch.queue_declare('funtest_survive') self.ch.queue_declare('funtest_survive', passive=True) self.assertEqual( 0, self.ch.queue_delete('funtest_survive'), ) def test_invalid_header(self): """ Test sending a message with an unserializable object in the header http://code.google.com/p/py-amqplib/issues/detail?id=17 """ qname, _, _ = self.ch.queue_declare() msg = Message(application_headers={'test': object()}) self.assertRaises( FrameSyntaxError, self.ch.basic_publish, msg, routing_key=qname, ) def test_large(self): """ Test sending some extra large messages. """ qname, _, _ = self.ch.queue_declare() for multiplier in [100, 1000, 10000]: msg = Message( 'funtest message' * multiplier, content_type='text/plain', application_headers={'foo': 7, 'bar': 'baz'}, ) self.ch.basic_publish(msg, routing_key=qname) msg2 = self.ch.basic_get(no_ack=True) self.assertEqual(msg, msg2) def test_publish(self): self.ch.exchange_declare('funtest.fanout', 'fanout', auto_delete=True) msg = Message( 'funtest message', content_type='text/plain', application_headers={'foo': 7, 'bar': 'baz'}, ) self.ch.basic_publish(msg, 'funtest.fanout') def test_queue(self): my_routing_key = 'funtest.test_queue' msg = Message( 'funtest message', content_type='text/plain', application_headers={'foo': 7, 'bar': 'baz'}, ) qname, _, _ = self.ch.queue_declare() self.ch.queue_bind(qname, 'amq.direct', routing_key=my_routing_key) self.ch.basic_publish(msg, 'amq.direct', routing_key=my_routing_key) msg2 = self.ch.basic_get(qname, no_ack=True) self.assertEqual(msg, msg2) def test_unbind(self): my_routing_key = 'funtest.test_queue' qname, _, _ = self.ch.queue_declare() self.ch.queue_bind(qname, 'amq.direct', routing_key=my_routing_key) self.ch.queue_unbind(qname, 'amq.direct', routing_key=my_routing_key) def test_basic_return(self): self.ch.exchange_declare('funtest.fanout', 'fanout', auto_delete=True) msg = Message( 'funtest message', content_type='text/plain', application_headers={'foo': 7, 'bar': 'baz'}) self.ch.basic_publish(msg, 'funtest.fanout') self.ch.basic_publish(msg, 'funtest.fanout', mandatory=True) self.ch.basic_publish(msg, 'funtest.fanout', mandatory=True) self.ch.basic_publish(msg, 'funtest.fanout', mandatory=True) self.ch.close() # # 3 of the 4 messages we sent should have been returned # self.assertEqual(self.ch.returned_messages.qsize(), 3) def test_exchange_bind(self): """Test exchange binding. Network configuration is as follows (-> is forwards to : source_exchange -> dest_exchange -> queue The test checks that once the message is publish to the destination exchange(funtest.topic_dest) it is delivered to the queue. """ test_routing_key = 'unit_test__key' dest_exchange = 'funtest.topic_dest_bind' source_exchange = 'funtest.topic_source_bind' self.ch.exchange_declare(dest_exchange, 'topic', auto_delete=True) self.ch.exchange_declare(source_exchange, 'topic', auto_delete=True) qname, _, _ = self.ch.queue_declare() self.ch.exchange_bind(destination=dest_exchange, source=source_exchange, routing_key=test_routing_key) self.ch.queue_bind(qname, dest_exchange, routing_key=test_routing_key) msg = Message('funtest message', content_type='text/plain', application_headers={'foo': 7, 'bar': 'baz'}) self.ch.basic_publish(msg, source_exchange, routing_key=test_routing_key) msg2 = self.ch.basic_get(qname, no_ack=True) self.assertEqual(msg, msg2) def test_exchange_unbind(self): dest_exchange = 'funtest.topic_dest_unbind' source_exchange = 'funtest.topic_source_unbind' test_routing_key = 'unit_test__key' self.ch.exchange_declare(dest_exchange, 'topic', auto_delete=True) self.ch.exchange_declare(source_exchange, 'topic', auto_delete=True) self.ch.exchange_bind(destination=dest_exchange, source=source_exchange, routing_key=test_routing_key) self.ch.exchange_unbind(destination=dest_exchange, source=source_exchange, routing_key=test_routing_key) def main(): suite = unittest.TestLoader().loadTestsFromTestCase(TestChannel) unittest.TextTestRunner(**settings.test_args).run(suite) if __name__ == '__main__': main() amqp-1.4.9/funtests/test_connection.py0000755000076500000000000000736012270020623020523 0ustar asksolwheel00000000000000#!/usr/bin/env python """Test amqp.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 unittest import settings from amqp import Connection class TestConnection(unittest.TestCase): def setUp(self): self.conn = Connection(**settings.connect_args) def tearDown(self): if self.conn: self.conn.close() def test_channel(self): ch = self.conn.channel(1) self.assertEqual(ch.channel_id, 1) ch2 = self.conn.channel() self.assertNotEqual(ch2.channel_id, 1) ch.close() ch2.close() def test_close(self): """Make sure we've broken various references when closing channels and connections, to help with GC.""" # # Create a channel and make sure it's linked as we'd expect # ch = self.conn.channel() self.assertEqual(1 in self.conn.channels, True) self.assertEqual(ch.connection, self.conn) self.assertEqual(ch.is_open, True) # # Close the channel and make sure the references are broken # that we expect. # ch.close() self.assertEqual(ch.connection, None) self.assertEqual(1 in self.conn.channels, False) self.assertEqual(ch.callbacks, {}) self.assertEqual(ch.is_open, False) # # Close the connection and make sure the references we expect # are gone. # self.conn.close() self.assertEqual(self.conn.connection, None) self.assertEqual(self.conn.channels, None) def test_gc_closed(self): """Make sure we've broken various references when closing channels and connections, to help with GC. gc.garbage: http://docs.python.org/library/gc.html#gc.garbage "A list of objects which the collector found to be unreachable but could not be freed (uncollectable objects)." """ unreachable_before = len(gc.garbage) # # Create a channel and make sure it's linked as we'd expect # self.conn.channel() self.assertEqual(1 in self.conn.channels, True) # # Close the connection and make sure the references we expect # are gone. # self.conn.close() gc.collect() gc.collect() gc.collect() self.assertEqual(unreachable_before, len(gc.garbage)) def test_gc_forget(self): """Make sure the connection gets gc'ed when there is no more references to it.""" unreachable_before = len(gc.garbage) ch = self.conn.channel() self.assertEqual(1 in self.conn.channels, True) # remove all the references self.conn = None del(ch) gc.collect() gc.collect() gc.collect() self.assertEqual(unreachable_before, len(gc.garbage)) def main(): suite = unittest.TestLoader().loadTestsFromTestCase(TestConnection) unittest.TextTestRunner(**settings.test_args).run(suite) if __name__ == '__main__': main() amqp-1.4.9/funtests/test_exceptions.py0000755000076500000000000000273112644026054020553 0ustar asksolwheel00000000000000#!/usr/bin/env python """ Test amqp.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 amqp.exceptions import AMQPError class TestException(unittest.TestCase): def test_exception(self): exc = AMQPError('My Error', (10, 10), reply_code=7) self.assertEqual(exc.reply_code, 7) self.assertEqual(exc.reply_text, 'My Error') self.assertEqual(exc.method_sig, (10, 10)) self.assertEqual( exc.args, (7, 'My Error', (10, 10), 'Connection.start'), ) def main(): suite = unittest.TestLoader().loadTestsFromTestCase(TestException) unittest.TextTestRunner(**settings.test_args).run(suite) if __name__ == '__main__': main() amqp-1.4.9/funtests/test_serialization.py0000755000076500000000000002507312270020623021242 0ustar asksolwheel00000000000000#!/usr/bin/env python """ Test amqp.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 import settings from amqp.serialization import ( AMQPReader, AMQPWriter, GenericContent, FrameSyntaxError, ) class TestSerialization(unittest.TestCase): if sys.version_info[0] >= 3: def assertEqualBinary(self, b, s): """ Helper for Py3k Compatibility """ self.assertEqual(b, s.encode('latin_1')) else: assertEqualBinary = unittest.TestCase.assertEqual def test_empty_writer(self): w = AMQPWriter() self.assertEqual(w.getvalue(), bytes()) # # Bits # def test_single_bit(self): for val, check in [(True, '\x01'), (False, '\x00')]: w = AMQPWriter() w.write_bit(val) s = w.getvalue() self.assertEqualBinary(s, check) r = AMQPReader(s) self.assertEqual(r.read_bit(), val) def test_multiple_bits(self): w = AMQPWriter() w.write_bit(True) w.write_bit(True) w.write_bit(False) w.write_bit(True) s = w.getvalue() self.assertEqualBinary(s, '\x0b') r = AMQPReader(s) self.assertEqual(r.read_bit(), True) self.assertEqual(r.read_bit(), True) self.assertEqual(r.read_bit(), False) self.assertEqual(r.read_bit(), True) def test_multiple_bits2(self): """ Check bits mixed with non-bits """ w = AMQPWriter() w.write_bit(True) w.write_bit(True) w.write_bit(False) w.write_octet(10) w.write_bit(True) s = w.getvalue() self.assertEqualBinary(s, '\x03\x0a\x01') r = AMQPReader(s) self.assertEqual(r.read_bit(), True) self.assertEqual(r.read_bit(), True) self.assertEqual(r.read_bit(), False) self.assertEqual(r.read_octet(), 10) self.assertEqual(r.read_bit(), True) def test_multiple_bits3(self): """ Check bit groups that span multiple bytes """ w = AMQPWriter() # Spit out 20 bits for i in range(10): w.write_bit(True) w.write_bit(False) s = w.getvalue() self.assertEqualBinary(s, '\x55\x55\x05') r = AMQPReader(s) for i in range(10): self.assertEqual(r.read_bit(), True) self.assertEqual(r.read_bit(), False) # # Octets # def test_octet(self): for val in range(256): w = AMQPWriter() w.write_octet(val) s = w.getvalue() self.assertEqualBinary(s, chr(val)) r = AMQPReader(s) self.assertEqual(r.read_octet(), val) def test_octet_invalid(self): w = AMQPWriter() self.assertRaises(FrameSyntaxError, w.write_octet, -1) def test_octet_invalid2(self): w = AMQPWriter() self.assertRaises(FrameSyntaxError, w.write_octet, 256) # # Shorts # def test_short(self): for i in range(256): val = randint(0, 65535) w = AMQPWriter() w.write_short(val) s = w.getvalue() r = AMQPReader(s) self.assertEqual(r.read_short(), val) def test_short_invalid(self): w = AMQPWriter() self.assertRaises(FrameSyntaxError, w.write_short, -1) def test_short_invalid2(self): w = AMQPWriter() self.assertRaises(FrameSyntaxError, w.write_short, 65536) # # Longs # def test_long(self): for i in range(256): val = randint(0, 4294967295) w = AMQPWriter() w.write_long(val) s = w.getvalue() r = AMQPReader(s) self.assertEqual(r.read_long(), val) def test_long_invalid(self): w = AMQPWriter() self.assertRaises(FrameSyntaxError, w.write_long, -1) def test_long_invalid2(self): w = AMQPWriter() self.assertRaises(FrameSyntaxError, w.write_long, 4294967296) # # LongLongs # def test_longlong(self): for i in range(256): val = randint(0, (2 ** 64) - 1) w = AMQPWriter() w.write_longlong(val) s = w.getvalue() r = AMQPReader(s) self.assertEqual(r.read_longlong(), val) def test_longlong_invalid(self): w = AMQPWriter() self.assertRaises(FrameSyntaxError, w.write_longlong, -1) def test_longlong_invalid2(self): w = AMQPWriter() self.assertRaises(FrameSyntaxError, w.write_longlong, 2 ** 64) # # Shortstr # def test_empty_shortstr(self): w = AMQPWriter() w.write_shortstr('') s = w.getvalue() self.assertEqualBinary(s, '\x00') r = AMQPReader(s) self.assertEqual(r.read_shortstr(), '') def test_shortstr(self): w = AMQPWriter() w.write_shortstr('hello') s = w.getvalue() self.assertEqualBinary(s, '\x05hello') r = AMQPReader(s) self.assertEqual(r.read_shortstr(), 'hello') def test_shortstr_unicode(self): w = AMQPWriter() w.write_shortstr(u'hello') s = w.getvalue() self.assertEqualBinary(s, '\x05hello') r = AMQPReader(s) self.assertEqual(r.read_shortstr(), u'hello') def test_long_shortstr(self): w = AMQPWriter() self.assertRaises(FrameSyntaxError, w.write_shortstr, 'x' * 256) def test_long_shortstr_unicode(self): w = AMQPWriter() self.assertRaises(FrameSyntaxError, w.write_shortstr, u'\u0100' * 128) # # Longstr # def test_empty_longstr(self): w = AMQPWriter() w.write_longstr('') s = w.getvalue() self.assertEqualBinary(s, '\x00\x00\x00\x00') r = AMQPReader(s) self.assertEqual(r.read_longstr(), '') def test_longstr(self): val = 'a' * 512 w = AMQPWriter() w.write_longstr(val) s = w.getvalue() self.assertEqualBinary(s, '\x00\x00\x02\x00' + ('a' * 512)) r = AMQPReader(s) self.assertEqual(r.read_longstr(), str(val)) def test_longstr_unicode(self): val = u'a' * 512 w = AMQPWriter() w.write_longstr(val) s = w.getvalue() self.assertEqualBinary(s, '\x00\x00\x02\x00' + ('a' * 512)) r = AMQPReader(s) self.assertEqual(r.read_longstr(), val) # # Table # def test_table_empty(self): val = {} w = AMQPWriter() w.write_table(val) s = w.getvalue() self.assertEqualBinary(s, '\x00\x00\x00\x00') r = AMQPReader(s) self.assertEqual(r.read_table(), val) def test_table(self): val = {'foo': 7} w = AMQPWriter() w.write_table(val) s = w.getvalue() self.assertEqualBinary(s, '\x00\x00\x00\x09\x03fooI\x00\x00\x00\x07') r = AMQPReader(s) self.assertEqual(r.read_table(), val) def test_table_invalid(self): """ Check that an un-serializable table entry raises a ValueError """ val = {'test': object()} w = AMQPWriter() self.assertRaises(FrameSyntaxError, w.write_table, val) def test_table_multi(self): val = { 'foo': 7, 'bar': Decimal('123345.1234'), 'baz': 'this is some random string I typed', 'ubaz': u'And something in unicode', 'dday_aniv': datetime(1994, 6, 6), 'nothing': None, 'more': { 'abc': -123, 'def': 'hello world', 'now': datetime(2007, 11, 11, 21, 14, 31), 'qty': Decimal('-123.45'), 'blank': {}, 'extra': { 'deeper': 'more strings', 'nums': -12345678, }, } } w = AMQPWriter() w.write_table(val) s = w.getvalue() r = AMQPReader(s) self.assertEqual(r.read_table(), val) # # Array # def test_array_from_list(self): val = [1, 'foo', None] w = AMQPWriter() w.write_array(val) s = w.getvalue() self.assertEqualBinary( s, '\x00\x00\x00\x0EI\x00\x00\x00\x01S\x00\x00\x00\x03fooV', ) r = AMQPReader(s) self.assertEqual(r.read_array(), val) def test_array_from_tuple(self): val = (1, 'foo', None) w = AMQPWriter() w.write_array(val) s = w.getvalue() self.assertEqualBinary( s, '\x00\x00\x00\x0EI\x00\x00\x00\x01S\x00\x00\x00\x03fooV', ) r = AMQPReader(s) self.assertEqual(r.read_array(), list(val)) def test_table_with_array(self): val = { 'foo': 7, 'bar': Decimal('123345.1234'), 'baz': 'this is some random string I typed', 'blist': [1, 2, 3], 'nlist': [1, [2, 3, 4]], 'ndictl': {'nfoo': 8, 'nblist': [5, 6, 7]} } w = AMQPWriter() w.write_table(val) s = w.getvalue() r = AMQPReader(s) self.assertEqual(r.read_table(), val) # # GenericContent # def test_generic_content_eq(self): msg_1 = GenericContent(dummy='foo') msg_2 = GenericContent(dummy='foo') msg_3 = GenericContent(dummy='bar') self.assertEqual(msg_1, msg_1) self.assertEqual(msg_1, msg_2) self.assertNotEqual(msg_1, msg_3) self.assertNotEqual(msg_1, None) def main(): suite = unittest.TestLoader().loadTestsFromTestCase(TestSerialization) unittest.TextTestRunner(**settings.test_args).run(suite) if __name__ == '__main__': main() amqp-1.4.9/funtests/test_with.py0000644000076500000000000000414012270020623017325 0ustar asksolwheel00000000000000#!/usr/bin/env python """ Test support for 'with' statements """ # 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 unittest import settings from amqp import Connection, Message class TestChannel(unittest.TestCase): def test_with(self): with Connection(**settings.connect_args) as conn: self.assertEqual(conn.transport is None, False) with conn.channel(1) as ch: self.assertEqual(1 in conn.channels, True) # # Do something with the channel # ch.exchange_declare('unittest.fanout', 'fanout', auto_delete=True) msg = Message( 'unittest message', content_type='text/plain', application_headers={'foo': 7, 'bar': 'baz'}, ) ch.basic_publish(msg, 'unittest.fanout') # # check that the channel was closed # self.assertEqual(1 in conn.channels, False) self.assertEqual(ch.is_open, False) # # Check that the connection was closed # self.assertEqual(conn.transport, None) def main(): suite = unittest.TestLoader().loadTestsFromTestCase(TestChannel) unittest.TextTestRunner(**settings.test_args).run(suite) if __name__ == '__main__': main() amqp-1.4.9/LICENSE0000644000076500000000000005750612270020623014111 0ustar asksolwheel00000000000000 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 amqp-1.4.9/MANIFEST.in0000644000076500000000000000030312270020623014621 0ustar asksolwheel00000000000000include README.rst Changelog LICENSE recursive-include docs * recursive-include demo *.py recursive-include extra README *.py recursive-include funtests *.py recursive-include requirements *.txt amqp-1.4.9/PKG-INFO0000644000076500000000000001331512644064471014205 0ustar asksolwheel00000000000000Metadata-Version: 1.1 Name: amqp Version: 1.4.9 Summary: Low-level AMQP client for Python (fork of amqplib) Home-page: http://github.com/celery/py-amqp Author: Ask Solem Author-email: pyamqp@celeryproject.org License: LGPL Description: ===================================================================== Python AMQP 0.9.1 client library ===================================================================== :Version: 1.4.9 :Web: http://amqp.readthedocs.org/ :Download: http://pypi.python.org/pypi/amqp/ :Source: http://github.com/celery/py-amqp/ :Keywords: amqp, rabbitmq About ===== This is a fork of amqplib_ which was originally written by Barry Pederson. It is maintained by the Celery_ project, and used by `kombu`_ as a pure python alternative when `librabbitmq`_ is not available. This library should be API compatible with `librabbitmq`_. .. _amqplib: http://pypi.python.org/pypi/amqplib .. _Celery: http://celeryproject.org/ .. _kombu: http://kombu.readthedocs.org/ .. _librabbitmq: http://pypi.python.org/pypi/librabbitmq Differences from `amqplib`_ =========================== - Supports draining events from multiple channels (``Connection.drain_events``) - Support for timeouts - Channels are restored after channel error, instead of having to close the connection. - Support for heartbeats - ``Connection.heartbeat_tick(rate=2)`` must called at regular intervals (half of the heartbeat value if rate is 2). - Or some other scheme by using ``Connection.send_heartbeat``. - Supports RabbitMQ extensions: - Consumer Cancel Notifications - by default a cancel results in ``ChannelError`` being raised - but not if a ``on_cancel`` callback is passed to ``basic_consume``. - Publisher confirms - ``Channel.confirm_select()`` enables publisher confirms. - ``Channel.events['basic_ack'].append(my_callback)`` adds a callback to be called when a message is confirmed. This callback is then called with the signature ``(delivery_tag, multiple)``. - Exchange-to-exchange bindings: ``exchange_bind`` / ``exchange_unbind``. - ``Channel.confirm_select()`` enables publisher confirms. - ``Channel.events['basic_ack'].append(my_callback)`` adds a callback to be called when a message is confirmed. This callback is then called with the signature ``(delivery_tag, multiple)``. - Support for ``basic_return`` - Uses AMQP 0-9-1 instead of 0-8. - ``Channel.access_request`` and ``ticket`` arguments to methods **removed**. - Supports the ``arguments`` argument to ``basic_consume``. - ``internal`` argument to ``exchange_declare`` removed. - ``auto_delete`` argument to ``exchange_declare`` deprecated - ``insist`` argument to ``Connection`` removed. - ``Channel.alerts`` has been removed. - Support for ``Channel.basic_recover_async``. - ``Channel.basic_recover`` deprecated. - Exceptions renamed to have idiomatic names: - ``AMQPException`` -> ``AMQPError`` - ``AMQPConnectionException`` -> ConnectionError`` - ``AMQPChannelException`` -> ChannelError`` - ``Connection.known_hosts`` removed. - ``Connection`` no longer supports redirects. - ``exchange`` argument to ``queue_bind`` can now be empty to use the "default exchange". - Adds ``Connection.is_alive`` that tries to detect whether the connection can still be used. - Adds ``Connection.connection_errors`` and ``.channel_errors``, a list of recoverable errors. - Exposes the underlying socket as ``Connection.sock``. - Adds ``Channel.no_ack_consumers`` to keep track of consumer tags that set the no_ack flag. - Slightly better at error recovery Further ======= - Differences between AMQP 0.8 and 0.9.1 http://www.rabbitmq.com/amqp-0-8-to-0-9-1.html - AMQP 0.9.1 Quick Reference http://www.rabbitmq.com/amqp-0-9-1-quickref.html - RabbitMQ Extensions http://www.rabbitmq.com/extensions.html - For more information about AMQP, visit http://www.amqp.org - For other Python client libraries see: http://www.rabbitmq.com/devtools.html#python-dev .. image:: https://d2weczhvl823v0.cloudfront.net/celery/celery/trend.png :alt: Bitdeli badge :target: https://bitdeli.com/free Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.0 Classifier: Programming Language :: Python :: 3.1 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent amqp-1.4.9/README.rst0000644000076500000000000000767212644064340014603 0ustar asksolwheel00000000000000===================================================================== Python AMQP 0.9.1 client library ===================================================================== :Version: 1.4.9 :Web: http://amqp.readthedocs.org/ :Download: http://pypi.python.org/pypi/amqp/ :Source: http://github.com/celery/py-amqp/ :Keywords: amqp, rabbitmq About ===== This is a fork of amqplib_ which was originally written by Barry Pederson. It is maintained by the Celery_ project, and used by `kombu`_ as a pure python alternative when `librabbitmq`_ is not available. This library should be API compatible with `librabbitmq`_. .. _amqplib: http://pypi.python.org/pypi/amqplib .. _Celery: http://celeryproject.org/ .. _kombu: http://kombu.readthedocs.org/ .. _librabbitmq: http://pypi.python.org/pypi/librabbitmq Differences from `amqplib`_ =========================== - Supports draining events from multiple channels (``Connection.drain_events``) - Support for timeouts - Channels are restored after channel error, instead of having to close the connection. - Support for heartbeats - ``Connection.heartbeat_tick(rate=2)`` must called at regular intervals (half of the heartbeat value if rate is 2). - Or some other scheme by using ``Connection.send_heartbeat``. - Supports RabbitMQ extensions: - Consumer Cancel Notifications - by default a cancel results in ``ChannelError`` being raised - but not if a ``on_cancel`` callback is passed to ``basic_consume``. - Publisher confirms - ``Channel.confirm_select()`` enables publisher confirms. - ``Channel.events['basic_ack'].append(my_callback)`` adds a callback to be called when a message is confirmed. This callback is then called with the signature ``(delivery_tag, multiple)``. - Exchange-to-exchange bindings: ``exchange_bind`` / ``exchange_unbind``. - ``Channel.confirm_select()`` enables publisher confirms. - ``Channel.events['basic_ack'].append(my_callback)`` adds a callback to be called when a message is confirmed. This callback is then called with the signature ``(delivery_tag, multiple)``. - Support for ``basic_return`` - Uses AMQP 0-9-1 instead of 0-8. - ``Channel.access_request`` and ``ticket`` arguments to methods **removed**. - Supports the ``arguments`` argument to ``basic_consume``. - ``internal`` argument to ``exchange_declare`` removed. - ``auto_delete`` argument to ``exchange_declare`` deprecated - ``insist`` argument to ``Connection`` removed. - ``Channel.alerts`` has been removed. - Support for ``Channel.basic_recover_async``. - ``Channel.basic_recover`` deprecated. - Exceptions renamed to have idiomatic names: - ``AMQPException`` -> ``AMQPError`` - ``AMQPConnectionException`` -> ConnectionError`` - ``AMQPChannelException`` -> ChannelError`` - ``Connection.known_hosts`` removed. - ``Connection`` no longer supports redirects. - ``exchange`` argument to ``queue_bind`` can now be empty to use the "default exchange". - Adds ``Connection.is_alive`` that tries to detect whether the connection can still be used. - Adds ``Connection.connection_errors`` and ``.channel_errors``, a list of recoverable errors. - Exposes the underlying socket as ``Connection.sock``. - Adds ``Channel.no_ack_consumers`` to keep track of consumer tags that set the no_ack flag. - Slightly better at error recovery Further ======= - Differences between AMQP 0.8 and 0.9.1 http://www.rabbitmq.com/amqp-0-8-to-0-9-1.html - AMQP 0.9.1 Quick Reference http://www.rabbitmq.com/amqp-0-9-1-quickref.html - RabbitMQ Extensions http://www.rabbitmq.com/extensions.html - For more information about AMQP, visit http://www.amqp.org - For other Python client libraries see: http://www.rabbitmq.com/devtools.html#python-dev .. image:: https://d2weczhvl823v0.cloudfront.net/celery/celery/trend.png :alt: Bitdeli badge :target: https://bitdeli.com/free amqp-1.4.9/requirements/0000755000076500000000000000000012644064471015630 5ustar asksolwheel00000000000000amqp-1.4.9/requirements/docs.txt0000644000076500000000000000004712270020623017304 0ustar asksolwheel00000000000000Sphinx sphinxcontrib-issuetracker>=0.9 amqp-1.4.9/requirements/pkgutils.txt0000644000076500000000000000005612644026054020227 0ustar asksolwheel00000000000000paver flake8 flakeplus tox Sphinx-PyPI-upload amqp-1.4.9/requirements/test.txt0000644000076500000000000000006512644026054017344 0ustar asksolwheel00000000000000unittest2>=0.4.0 nose nose-cover3 coverage>=3.0 mock amqp-1.4.9/setup.cfg0000644000076500000000000000017012644064471014724 0ustar asksolwheel00000000000000[nosetests] where = amqp/tests [bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 amqp-1.4.9/setup.py0000644000076500000000000000654512644026054014624 0ustar asksolwheel00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- try: from setuptools import setup, find_packages from setuptools.command.test import test except ImportError: raise from ez_setup import use_setuptools use_setuptools() from setuptools import setup, find_packages # noqa from setuptools.command.test import test # noqa import os import sys import codecs if sys.version_info < (2, 6): raise Exception('amqp requires Python 2.6 or higher.') NAME = 'amqp' entrypoints = {} extra = {} # -*- Classifiers -*- classes = """ Development Status :: 5 - Production/Stable Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.6 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.0 Programming Language :: Python :: 3.1 Programming Language :: Python :: 3.2 Programming Language :: Python :: 3.3 License :: OSI Approved :: GNU Library or \ Lesser General Public License (LGPL) Intended Audience :: Developers License :: OSI Approved :: BSD License Operating System :: OS Independent """ classifiers = [s.strip() for s in classes.split('\n') if s] # -*- Distribution Meta -*- import re re_meta = re.compile(r'__(\w+?)__\s*=\s*(.*)') re_vers = re.compile(r'VERSION\s*=\s*\((.*?)\)') re_doc = re.compile(r'^"""(.+?)"""') rq = lambda s: s.strip("\"'") def add_default(m): attr_name, attr_value = m.groups() return ((attr_name, rq(attr_value)), ) def add_version(m): v = list(map(rq, m.groups()[0].split(', '))) return (('VERSION', '.'.join(v[0:3]) + ''.join(v[3:])), ) def add_doc(m): return (('doc', m.groups()[0]), ) pats = {re_meta: add_default, re_vers: add_version, re_doc: add_doc} here = os.path.abspath(os.path.dirname(__file__)) with open(os.path.join(here, 'amqp/__init__.py')) as meta_fh: meta = {} for line in meta_fh: if line.strip() == '# -eof meta-': break for pattern, handler in pats.items(): m = pattern.match(line.strip()) if m: meta.update(handler(m)) # -*- Installation Requires -*- py_version = sys.version_info is_jython = sys.platform.startswith('java') is_pypy = hasattr(sys, 'pypy_version_info') def strip_comments(l): return l.split('#', 1)[0].strip() def reqs(f): return filter(None, [strip_comments(l) for l in open( os.path.join(os.getcwd(), 'requirements', f)).readlines()]) install_requires = [] # -*- Tests Requires -*- tests_require = reqs('test.txt') # -*- Long Description -*- if os.path.exists('README.rst'): long_description = codecs.open('README.rst', 'r', 'utf-8').read() else: long_description = 'See http://pypi.python.org/pypi/amqp' # -*- Entry Points -*- # # -*- %%% -*- setup( name=NAME, version=meta['VERSION'], description=meta['doc'], author=meta['author'], author_email=meta['contact'], maintainer=meta['maintainer'], url=meta['homepage'], platforms=['any'], license='LGPL', packages=find_packages(exclude=['ez_setup', 'tests', 'tests.*']), zip_safe=False, install_requires=install_requires, tests_require=tests_require, test_suite='nose.collector', classifiers=classifiers, entry_points=entrypoints, long_description=long_description, **extra)