amqp-2.2.2/0000755000175000017500000000000013156466127012414 5ustar omeromer00000000000000amqp-2.2.2/MANIFEST.in0000644000175000017500000000046513131450043014136 0ustar omeromer00000000000000include README.rst Changelog LICENSE recursive-include docs * recursive-include demo *.py recursive-include extra README *.py recursive-include requirements *.txt recursive-include t *.py recursive-exclude docs/_build * recursive-exclude * __pycache__ recursive-exclude * *.py[co] recursive-exclude * .*.sw* amqp-2.2.2/t/0000755000175000017500000000000013156466127012657 5ustar omeromer00000000000000amqp-2.2.2/t/unit/0000755000175000017500000000000013156466127013636 5ustar omeromer00000000000000amqp-2.2.2/t/unit/test_platform.py0000644000175000017500000000103113131450043017045 0ustar omeromer00000000000000from __future__ import absolute_import, unicode_literals import pytest from amqp.platform import _linux_version_to_tuple def test_struct_argument_type(): from amqp.exceptions import FrameSyntaxError FrameSyntaxError() @pytest.mark.parametrize('s,expected', [ ('3.13.0-46-generic', (3, 13, 0)), ('3.19.43-1-amd64', (3, 19, 43)), ('4.4.34+', (4, 4, 34)), ('4.4.what', (4, 4, 0)), ('4.what.what', (4, 0, 0)), ]) def test_linux_version_to_tuple(s, expected): assert _linux_version_to_tuple(s) == expected amqp-2.2.2/t/unit/test_exceptions.py0000644000175000017500000000103413131450043017405 0ustar omeromer00000000000000from __future__ import absolute_import, unicode_literals from case import Mock from amqp.exceptions import AMQPError, error_for_code class test_AMQPError: def test_str(self): assert str(AMQPError()) x = AMQPError(method_sig=(50, 60)) assert str(x) class test_error_for_code: def test_unknown_error(self): default = Mock(name='default') x = error_for_code(2134214314, 't', 'm', default) default.assert_called_with('t', 'm', reply_code=2134214314) assert x is default() amqp-2.2.2/t/unit/__init__.py0000644000175000017500000000000013131450043015714 0ustar omeromer00000000000000amqp-2.2.2/t/unit/test_transport.py0000644000175000017500000002667013131450043017275 0ustar omeromer00000000000000from __future__ import absolute_import, unicode_literals import errno import socket import pytest from case import Mock, patch from amqp import transport from amqp.exceptions import UnexpectedFrame from amqp.platform import pack class MockSocket(object): options = {} def setsockopt(self, family, key, value): if not isinstance(value, int): raise socket.error() self.options[key] = value def getsockopt(self, family, key): return self.options.get(key, 0) TCP_KEEPIDLE = 4 TCP_KEEPINTVL = 5 TCP_KEEPCNT = 6 class test_socket_options: @pytest.fixture(autouse=True) def setup_self(self, patching): self.host = '127.0.0.1' self.connect_timeout = 3 self.socket = MockSocket() try: import fcntl except ImportError: fcntl = None if fcntl is not None: patching('fcntl.fcntl') socket = patching('socket.socket') socket().getsockopt = self.socket.getsockopt socket().setsockopt = self.socket.setsockopt self.tcp_keepidle = 20 self.tcp_keepintvl = 30 self.tcp_keepcnt = 40 self.socket.setsockopt( socket.SOL_TCP, socket.TCP_NODELAY, 1, ) self.socket.setsockopt( socket.SOL_TCP, TCP_KEEPIDLE, self.tcp_keepidle, ) self.socket.setsockopt( socket.SOL_TCP, TCP_KEEPINTVL, self.tcp_keepintvl, ) self.socket.setsockopt( socket.SOL_TCP, TCP_KEEPCNT, self.tcp_keepcnt, ) patching('amqp.transport.TCPTransport._write') patching('amqp.transport.TCPTransport._setup_transport') patching('amqp.transport.SSLTransport._write') patching('amqp.transport.SSLTransport._setup_transport') def test_backward_compatibility_tcp_transport(self): self.transp = transport.Transport( self.host, self.connect_timeout, ssl=False, ) self.transp.connect() expected = 1 result = self.socket.getsockopt(socket.SOL_TCP, socket.TCP_NODELAY) assert result == expected def test_backward_compatibility_SSL_transport(self): self.transp = transport.Transport( self.host, self.connect_timeout, ssl=True, ) assert self.transp.sslopts is not None self.transp.connect() assert self.transp.sock is not None def test_use_default_sock_tcp_opts(self): self.transp = transport.Transport( self.host, self.connect_timeout, socket_settings={}, ) self.transp.connect() assert (socket.TCP_NODELAY in self.transp._get_tcp_socket_defaults(self.transp.sock)) def test_set_single_sock_tcp_opt_tcp_transport(self): tcp_keepidle = self.tcp_keepidle + 5 socket_settings = {TCP_KEEPIDLE: tcp_keepidle} self.transp = transport.Transport( self.host, self.connect_timeout, ssl=False, socket_settings=socket_settings, ) self.transp.connect() expected = tcp_keepidle result = self.socket.getsockopt(socket.SOL_TCP, TCP_KEEPIDLE) assert result == expected def test_set_single_sock_tcp_opt_SSL_transport(self): self.tcp_keepidle += 5 socket_settings = {TCP_KEEPIDLE: self.tcp_keepidle} self.transp = transport.Transport( self.host, self.connect_timeout, ssl=True, socket_settings=socket_settings, ) self.transp.connect() expected = self.tcp_keepidle result = self.socket.getsockopt(socket.SOL_TCP, TCP_KEEPIDLE) assert result == expected def test_values_are_set(self): socket_settings = { TCP_KEEPIDLE: 10, TCP_KEEPINTVL: 4, TCP_KEEPCNT: 2 } self.transp = transport.Transport( self.host, self.connect_timeout, socket_settings=socket_settings, ) self.transp.connect() expected = socket_settings tcp_keepidle = self.socket.getsockopt(socket.SOL_TCP, TCP_KEEPIDLE) tcp_keepintvl = self.socket.getsockopt(socket.SOL_TCP, TCP_KEEPINTVL) tcp_keepcnt = self.socket.getsockopt(socket.SOL_TCP, TCP_KEEPCNT) result = { TCP_KEEPIDLE: tcp_keepidle, TCP_KEEPINTVL: tcp_keepintvl, TCP_KEEPCNT: tcp_keepcnt } assert result == expected def test_passing_wrong_options(self): socket_settings = object() self.transp = transport.Transport( self.host, self.connect_timeout, socket_settings=socket_settings, ) with pytest.raises(TypeError): self.transp.connect() def test_passing_wrong_value_options(self): socket_settings = {TCP_KEEPINTVL: 'a'.encode()} self.transp = transport.Transport( self.host, self.connect_timeout, socket_settings=socket_settings, ) with pytest.raises(socket.error): self.transp.connect() def test_passing_value_as_string(self): socket_settings = {TCP_KEEPIDLE: '5'.encode()} self.transp = transport.Transport( self.host, self.connect_timeout, socket_settings=socket_settings, ) with pytest.raises(socket.error): self.transp.connect() def test_passing_tcp_nodelay(self): socket_settings = {socket.TCP_NODELAY: 0} self.transp = transport.Transport( self.host, self.connect_timeout, socket_settings=socket_settings, ) self.transp.connect() expected = 0 result = self.socket.getsockopt(socket.SOL_TCP, socket.TCP_NODELAY) assert result == expected class test_AbstractTransport: class Transport(transport._AbstractTransport): def _connect(self, *args): pass def _init_socket(self, *args): pass @pytest.fixture(autouse=True) def setup_transport(self): self.t = self.Transport('localhost:5672', 10) self.t.connect() def test_port(self): assert self.Transport('localhost').port == 5672 assert self.Transport('localhost:5672').port == 5672 assert self.Transport('[fe80::1]:5432').port == 5432 def test_read(self): with pytest.raises(NotImplementedError): self.t._read(1024) def test_setup_transport(self): self.t._setup_transport() def test_shutdown_transport(self): self.t._shutdown_transport() def test_write(self): with pytest.raises(NotImplementedError): self.t._write('foo') def test_close(self): sock = self.t.sock = Mock() self.t.close() sock.shutdown.assert_called_with(socket.SHUT_RDWR) sock.close.assert_called_with() assert self.t.sock is None self.t.close() def test_read_frame__timeout(self): self.t._read = Mock() self.t._read.side_effect = socket.timeout() with pytest.raises(socket.timeout): self.t.read_frame() def test_read_frame__SSLError(self): self.t._read = Mock() self.t._read.side_effect = transport.SSLError('timed out') with pytest.raises(socket.timeout): self.t.read_frame() def test_read_frame__EINTR(self): self.t._read = Mock() self.t.connected = True exc = OSError() exc.errno = errno.EINTR self.t._read.side_effect = exc with pytest.raises(OSError): self.t.read_frame() assert self.t.connected def test_read_frame__EBADF(self): self.t._read = Mock() self.t.connected = True exc = OSError() exc.errno = errno.EBADF self.t._read.side_effect = exc with pytest.raises(OSError): self.t.read_frame() assert not self.t.connected def test_read_frame__simple(self): self.t._read = Mock() checksum = [b'\xce'] def on_read2(size, *args): return checksum[0] def on_read1(size, *args): ret = self.t._read.return_value self.t._read.return_value = b'thequickbrownfox' self.t._read.side_effect = on_read2 return ret self.t._read.return_value = pack('>BHI', 1, 1, 16) self.t._read.side_effect = on_read1 self.t.read_frame() self.t._read.return_value = pack('>BHI', 1, 1, 16) self.t._read.side_effect = on_read1 checksum[0] = b'\x13' with pytest.raises(UnexpectedFrame): self.t.read_frame() def test_write__success(self): self.t._write = Mock() self.t.write('foo') self.t._write.assert_called_with('foo') def test_write__socket_timeout(self): self.t._write = Mock() self.t._write.side_effect = socket.timeout with pytest.raises(socket.timeout): self.t.write('foo') def test_write__EINTR(self): self.t.connected = True self.t._write = Mock() exc = OSError() exc.errno = errno.EINTR self.t._write.side_effect = exc with pytest.raises(OSError): self.t.write('foo') assert self.t.connected exc.errno = errno.EBADF with pytest.raises(OSError): self.t.write('foo') assert not self.t.connected class test_SSLTransport: class Transport(transport.SSLTransport): def _connect(self, *args): pass def _init_socket(self, *args): pass @pytest.fixture(autouse=True) def setup_transport(self): self.t = self.Transport( 'fe80::9a5a:ebff::fecb::ad1c:30', 3, ssl={'foo': 30}, ) def test_setup_transport(self): sock = self.t.sock = Mock() self.t._wrap_socket = Mock() self.t._setup_transport() self.t._wrap_socket.assert_called_with(sock, foo=30) self.t.sock.do_handshake.assert_called_with() assert self.t._quick_recv is self.t.sock.read def test_wrap_socket(self): sock = Mock() self.t._wrap_context = Mock() self.t._wrap_socket_sni = Mock() self.t._wrap_socket(sock, foo=1) self.t._wrap_socket_sni.assert_called_with(sock, foo=1) self.t._wrap_socket(sock, {'c': 2}, foo=1) self.t._wrap_context.assert_called_with(sock, {'foo': 1}, c=2) @patch('ssl.create_default_context', create=True) def test_wrap_context(self, create_default_context): sock = Mock() self.t._wrap_context(sock, {'f': 1}, check_hostname=True, bar=3) create_default_context.assert_called_with(bar=3) ctx = create_default_context() assert ctx.check_hostname ctx.wrap_socket.assert_called_with(sock, f=1) def test_shutdown_transport(self): self.t.sock = None self.t._shutdown_transport() self.t.sock = object() self.t._shutdown_transport() sock = self.t.sock = Mock() self.t._shutdown_transport() assert self.t.sock is sock.unwrap() class test_TCPTransport: class Transport(transport.TCPTransport): def _connect(self, *args): pass def _init_socket(self, *args): pass @pytest.fixture(autouse=True) def setup_transport(self): self.t = self.Transport('host', 3) def test_setup_transport(self): self.t.sock = Mock() self.t._setup_transport() assert self.t._write is self.t.sock.sendall assert self.t._read_buffer is not None assert self.t._quick_recv is self.t.sock.recv amqp-2.2.2/t/unit/test_serialization.py0000644000175000017500000001362213131450043020107 0ustar omeromer00000000000000from __future__ import absolute_import, unicode_literals import pytest from datetime import datetime from decimal import Decimal from math import ceil from amqp.basic_message import Message from amqp.exceptions import FrameSyntaxError from amqp.platform import pack from amqp.serialization import GenericContent, _read_item, dumps, loads class _ANY(object): def __eq__(self, other): return other is not None def __ne__(self, other): return other is None class test_serialization: @pytest.mark.parametrize('descr,frame,expected,cast', [ ('S', b's8thequick', 'thequick', None), ('b', b'b' + pack('>B', True), True, None), ('B', b'B' + pack('>b', 123), 123, None), ('U', b'U' + pack('>h', -321), -321, None), ('u', b'u' + pack('>H', 321), 321, None), ('i', b'i' + pack('>I', 1234), 1234, None), ('L', b'L' + pack('>q', -32451), -32451, None), ('l', b'l' + pack('>Q', 32451), 32451, None), ('f', b'f' + pack('>f', 33.3), 34.0, ceil), ]) def test_read_item(self, descr, frame, expected, cast): actual = _read_item(frame)[0] actual = cast(actual) if cast else actual assert actual == expected def test_read_item_V(self): assert _read_item(b'V')[0] is None def test_roundtrip(self): format = b'bobBlLbsbST' x = dumps(format, [ True, 32, False, 3415, 4513134, 13241923419, True, b'thequickbrownfox', False, 'jumpsoverthelazydog', datetime(2015, 3, 13, 10, 23), ]) y = loads(format, x) assert [ True, 32, False, 3415, 4513134, 13241923419, True, 'thequickbrownfox', False, 'jumpsoverthelazydog', datetime(2015, 3, 13, 10, 23), ] == y[0] def test_int_boundaries(self): format = b'F' x = dumps(format, [ {'a': -2147483649, 'b': 2147483648}, # celery/celery#3121 ]) y = loads(format, x) assert y[0] == [{ 'a': -2147483649, 'b': 2147483648, # celery/celery#3121 }] def test_loads_unknown_type(self): with pytest.raises(FrameSyntaxError): loads('x', 'asdsad') def test_float(self): assert (int(loads(b'fb', dumps(b'fb', [32.31, False]))[0][0] * 100) == 3231) def test_table(self): table = {'foo': 32, 'bar': 'baz', 'nil': None} assert loads(b'F', dumps(b'F', [table]))[0][0] == table def test_array(self): array = [ 'A', 1, True, 33.3, Decimal('55.5'), Decimal('-3.4'), datetime(2015, 3, 13, 10, 23), {'quick': 'fox', 'amount': 1}, [3, 'hens'], None, ] expected = list(array) expected[6] = _ANY() assert expected == loads('A', dumps('A', [array]))[0][0] def test_array_unknown_type(self): with pytest.raises(FrameSyntaxError): dumps('A', [[object()]]) class test_GenericContent: @pytest.fixture(autouse=True) def setup_content(self): self.g = GenericContent() def test_getattr(self): self.g.properties['foo'] = 30 with pytest.raises(AttributeError): self.g.__setstate__ assert self.g.foo == 30 with pytest.raises(AttributeError): self.g.bar def test_load_properties(self): m = Message() m.properties = { 'content_type': 'application/json', 'content_encoding': 'utf-8', 'application_headers': { 'foo': 1, 'id': 'id#1', }, 'delivery_mode': 1, 'priority': 255, 'correlation_id': 'df31-142f-34fd-g42d', 'reply_to': 'cosmo', 'expiration': '2015-12-23', 'message_id': '3312', 'timestamp': 3912491234, 'type': 'generic', 'user_id': 'george', 'app_id': 'vandelay', 'cluster_id': 'NYC', } s = m._serialize_properties() m2 = Message() m2._load_properties(m2.CLASS_ID, s) assert m2.properties == m.properties def test_load_properties__some_missing(self): m = Message() m.properties = { 'content_type': 'application/json', 'content_encoding': 'utf-8', 'delivery_mode': 1, 'correlation_id': 'df31-142f-34fd-g42d', 'reply_to': 'cosmo', 'expiration': '2015-12-23', 'message_id': '3312', 'type': None, 'app_id': None, 'cluster_id': None, } s = m._serialize_properties() m2 = Message() m2._load_properties(m2.CLASS_ID, s) def test_inbound_header(self): m = Message() m.properties = { 'content_type': 'application/json', 'content_encoding': 'utf-8', } body = 'the quick brown fox' buf = b'\0' * 30 + pack('>HxxQ', m.CLASS_ID, len(body)) buf += m._serialize_properties() assert m.inbound_header(buf, offset=30) == 42 assert m.body_size == len(body) assert m.properties['content_type'] == 'application/json' assert not m.ready def test_inbound_header__empty_body(self): m = Message() m.properties = {} buf = pack('>HxxQ', m.CLASS_ID, 0) buf += m._serialize_properties() assert m.inbound_header(buf, offset=0) == 12 assert m.ready def test_inbound_body(self): m = Message() m.body_size = 16 m.body_received = 8 m._pending_chunks = [b'the', b'quick'] m.inbound_body(b'brown') assert not m.ready m.inbound_body(b'fox') assert m.ready assert m.body == b'thequickbrownfox' def test_inbound_body__no_chunks(self): m = Message() m.body_size = 16 m.inbound_body('thequickbrownfox') assert m.ready amqp-2.2.2/t/unit/test_utils.py0000644000175000017500000000333613131450043016373 0ustar omeromer00000000000000from __future__ import absolute_import, unicode_literals from case import Mock, patch from amqp.five import text_t from amqp.utils import ( get_errno, coro, str_to_bytes, bytes_to_str, NullHandler, get_logger, ) class test_get_errno: def test_has_attr(self): exc = KeyError('foo') exc.errno = 23 assert get_errno(exc) == 23 def test_in_args(self): exc = KeyError(34, 'foo') exc.args = (34, 'foo') assert get_errno(exc) == 34 def test_args_short(self): exc = KeyError(34) assert not get_errno(exc) def test_no_args(self): assert not get_errno(object()) class test_coro: def test_advances(self): @coro def x(): yield 1 yield 2 it = x() assert next(it) == 2 class test_str_to_bytes: def test_from_unicode(self): assert isinstance(str_to_bytes('foo'), bytes) def test_from_bytes(self): assert isinstance(str_to_bytes(b'foo'), bytes) class test_bytes_to_str: def test_from_unicode(self): assert isinstance(bytes_to_str('foo'), text_t) def test_from_bytes(self): assert bytes_to_str(b'foo') class test_NullHandler: def test_emit(self): NullHandler().emit(Mock(name='record')) class test_get_logger: @patch('logging.getLogger') def test_as_str(self, getLogger): x = get_logger('foo.bar') getLogger.assert_called_with('foo.bar') assert x is getLogger() @patch('amqp.utils.NullHandler') def test_as_logger(self, _NullHandler): m = Mock(name='logger') m.handlers = None x = get_logger(m) assert x is m x.addHandler.assert_called_with(_NullHandler()) amqp-2.2.2/t/unit/test_method_framing.py0000644000175000017500000000642013154420412020215 0ustar omeromer00000000000000from __future__ import absolute_import, unicode_literals import pytest from case import Mock from amqp import spec from amqp.basic_message import Message from amqp.exceptions import UnexpectedFrame from amqp.method_framing import frame_handler, frame_writer from amqp.platform import pack class test_frame_handler: @pytest.fixture(autouse=True) def setup_conn(self): self.conn = Mock(name='connection') self.conn.bytes_recv = 0 self.callback = Mock(name='callback') self.g = frame_handler(self.conn, self.callback) def test_header(self): buf = pack('>HH', 60, 51) self.g((1, 1, buf)) self.callback.assert_called_with(1, (60, 51), buf, None) assert self.conn.bytes_recv def test_header_message_empty_body(self): self.g((1, 1, pack('>HH', *spec.Basic.Deliver))) self.callback.assert_not_called() with pytest.raises(UnexpectedFrame): self.g((1, 1, pack('>HH', *spec.Basic.Deliver))) m = Message() m.properties = {} buf = pack('>HxxQ', m.CLASS_ID, 0) buf += m._serialize_properties() self.g((2, 1, buf)) self.callback.assert_called() msg = self.callback.call_args[0][3] self.callback.assert_called_with( 1, msg.frame_method, msg.frame_args, msg, ) def test_header_message_content(self): self.g((1, 1, pack('>HH', *spec.Basic.Deliver))) self.callback.assert_not_called() m = Message() m.properties = {} buf = pack('>HxxQ', m.CLASS_ID, 16) buf += m._serialize_properties() self.g((2, 1, buf)) self.callback.assert_not_called() self.g((3, 1, b'thequick')) self.callback.assert_not_called() self.g((3, 1, b'brownfox')) self.callback.assert_called() msg = self.callback.call_args[0][3] self.callback.assert_called_with( 1, msg.frame_method, msg.frame_args, msg, ) assert msg.body == b'thequickbrownfox' def test_heartbeat_frame(self): self.g((8, 1, '')) assert self.conn.bytes_recv class test_frame_writer: @pytest.fixture(autouse=True) def setup_conn(self): self.connection = Mock(name='connection') self.transport = self.connection.Transport() self.connection.frame_max = 512 self.connection.bytes_sent = 0 self.g = frame_writer(self.connection, self.transport) self.write = self.transport.write def test_write_fast_header(self): frame = 1, 1, spec.Queue.Declare, b'x' * 30, None self.g(*frame) self.write.assert_called() def test_write_fast_content(self): msg = Message(body=b'y' * 10, content_type='utf-8') frame = 2, 1, spec.Basic.Publish, b'x' * 10, msg self.g(*frame) self.write.assert_called() def test_write_slow_content(self): msg = Message(body=b'y' * 2048, content_type='utf-8') frame = 2, 1, spec.Basic.Publish, b'x' * 10, msg self.g(*frame) self.write.assert_called() def test_write_zero_len_body(self): msg = Message(body=b'', content_type='application/octet-stream') frame = 2, 1, spec.Basic.Publish, b'x' * 10, msg self.g(*frame) self.write.assert_called() amqp-2.2.2/t/unit/test_sasl.py0000644000175000017500000001314713131450043016176 0ustar omeromer00000000000000from __future__ import absolute_import, unicode_literals import contextlib import socket from io import BytesIO from case import Mock, patch, call import pytest import sys from amqp import sasl from amqp.serialization import _write_table class test_SASL: def test_sasl_notimplemented(self): mech = sasl.SASL() with pytest.raises(NotImplementedError): mech.mechanism with pytest.raises(NotImplementedError): mech.start(None) def test_plain(self): username, password = 'foo', 'bar' mech = sasl.PLAIN(username, password) response = mech.start(None) assert isinstance(response, bytes) assert response.split(b'\0') == \ [b'', username.encode('utf-8'), password.encode('utf-8')] def test_amqplain(self): username, password = 'foo', 'bar' mech = sasl.AMQPLAIN(username, password) response = mech.start(None) assert isinstance(response, bytes) login_response = BytesIO() _write_table({b'LOGIN': username, b'PASSWORD': password}, login_response.write, []) expected_response = login_response.getvalue()[4:] assert response == expected_response def test_gssapi_missing(self): gssapi = sys.modules.pop('gssapi', None) GSSAPI = sasl._get_gssapi_mechanism() with pytest.raises(NotImplementedError): GSSAPI() if gssapi is not None: sys.modules['gssapi'] = gssapi @contextlib.contextmanager def fake_gssapi(self): orig_gssapi = sys.modules.pop('gssapi', None) orig_gssapi_raw = sys.modules.pop('gssapi.raw', None) orig_gssapi_raw_misc = sys.modules.pop('gssapi.raw.misc', None) gssapi = sys.modules['gssapi'] = Mock() sys.modules['gssapi.raw'] = gssapi.raw sys.modules['gssapi.raw.misc'] = gssapi.raw.misc class GSSError(Exception): pass gssapi.raw.misc.GSSError = GSSError try: yield gssapi finally: if orig_gssapi is None: del sys.modules['gssapi'] else: sys.modules['gssapi'] = orig_gssapi if orig_gssapi_raw is None: del sys.modules['gssapi.raw'] else: sys.modules['gssapi.raw'] = orig_gssapi_raw if orig_gssapi_raw_misc is None: del sys.modules['gssapi.raw.misc'] else: sys.modules['gssapi.raw.misc'] = orig_gssapi_raw_misc @patch('socket.gethostbyaddr') def test_gssapi_rdns(self, gethostbyaddr): with self.fake_gssapi() as gssapi: connection = Mock() connection.transport.sock.getpeername.return_value = ('192.0.2.0', 5672) connection.transport.sock.family = socket.AF_INET gethostbyaddr.return_value = ('broker.example.org', (), ()) GSSAPI = sasl._get_gssapi_mechanism() mech = GSSAPI(rdns=True) mech.start(connection) connection.transport.sock.getpeername.assert_called() gethostbyaddr.assert_called_with('192.0.2.0') gssapi.Name.assert_called_with(b'amqp@broker.example.org', gssapi.NameType.hostbased_service) def test_gssapi_no_rdns(self): with self.fake_gssapi() as gssapi: connection = Mock() connection.transport.host = 'broker.example.org' GSSAPI = sasl._get_gssapi_mechanism() mech = GSSAPI() mech.start(connection) gssapi.Name.assert_called_with(b'amqp@broker.example.org', gssapi.NameType.hostbased_service) def test_gssapi_step_without_client_name(self): with self.fake_gssapi() as gssapi: context = Mock() context.step.return_value = b'secrets' name = Mock() gssapi.SecurityContext.return_value = context gssapi.Name.return_value = name connection = Mock() connection.transport.host = 'broker.example.org' GSSAPI = sasl._get_gssapi_mechanism() mech = GSSAPI() response = mech.start(connection) gssapi.SecurityContext.assert_called_with(name=name, creds=None) context.step.assert_called_with(None) assert response == b'secrets' def test_gssapi_step_with_client_name(self): with self.fake_gssapi() as gssapi: context = Mock() context.step.return_value = b'secrets' client_name, service_name, credentials = Mock(), Mock(), Mock() gssapi.SecurityContext.return_value = context gssapi.Credentials.return_value = credentials gssapi.Name.side_effect = [client_name, service_name] connection = Mock() connection.transport.host = 'broker.example.org' GSSAPI = sasl._get_gssapi_mechanism() mech = GSSAPI(client_name='amqp-client/client.example.org') response = mech.start(connection) gssapi.Name.assert_has_calls([ call(b'amqp-client/client.example.org'), call(b'amqp@broker.example.org', gssapi.NameType.hostbased_service)]) gssapi.Credentials.assert_called_with(name=client_name) gssapi.SecurityContext.assert_called_with(name=service_name, creds=credentials) context.step.assert_called_with(None) assert response == b'secrets' amqp-2.2.2/t/unit/test_connection.py0000644000175000017500000003552513132055736017412 0ustar omeromer00000000000000from __future__ import absolute_import, unicode_literals import pytest import socket import warnings from case import ContextMock, Mock, call from amqp import Connection from amqp import spec from amqp.connection import SSLError from amqp.exceptions import ConnectionError, NotFound, ResourceError from amqp.five import items from amqp.sasl import SASL, AMQPLAIN, PLAIN from amqp.transport import TCPTransport class test_Connection: @pytest.fixture(autouse=True) def setup_conn(self): self.frame_handler = Mock(name='frame_handler') self.frame_writer = Mock(name='frame_writer_cls') self.conn = Connection( frame_handler=self.frame_handler, frame_writer=self.frame_writer, authentication=AMQPLAIN('foo', 'bar'), ) self.conn.Channel = Mock(name='Channel') self.conn.Transport = Mock(name='Transport') self.conn.transport = self.conn.Transport.return_value self.conn.send_method = Mock(name='send_method') self.conn.frame_writer = Mock(name='frame_writer') def test_sasl_authentication(self): authentication = SASL() self.conn = Connection(authentication=authentication) assert self.conn.authentication == (authentication,) def test_sasl_authentication_iterable(self): authentication = SASL() self.conn = Connection(authentication=(authentication,)) assert self.conn.authentication == (authentication,) def test_amqplain(self): self.conn = Connection(userid='foo', password='bar') assert isinstance(self.conn.authentication[1], AMQPLAIN) assert self.conn.authentication[1].username == 'foo' assert self.conn.authentication[1].password == 'bar' def test_plain(self): self.conn = Connection(userid='foo', password='bar') assert isinstance(self.conn.authentication[2], PLAIN) assert self.conn.authentication[2].username == 'foo' assert self.conn.authentication[2].password == 'bar' def test_enter_exit(self): self.conn.connect = Mock(name='connect') self.conn.close = Mock(name='close') with self.conn: self.conn.connect.assert_called_with() self.conn.close.assert_called_with() def test_then(self): self.conn.on_open = Mock(name='on_open') on_success = Mock(name='on_success') on_error = Mock(name='on_error') self.conn.then(on_success, on_error) self.conn.on_open.then.assert_called_with(on_success, on_error) def test_connect(self): self.conn.transport.connected = False self.conn.drain_events = Mock(name='drain_events') def on_drain(*args, **kwargs): self.conn._handshake_complete = True self.conn.drain_events.side_effect = on_drain self.conn.connect() self.conn.Transport.assert_called_with( self.conn.host, self.conn.connect_timeout, self.conn.ssl, self.conn.read_timeout, self.conn.write_timeout, socket_settings=self.conn.socket_settings, ) def test_connect__already_connected(self): callback = Mock(name='callback') self.conn.transport.connected = True assert self.conn.connect(callback) == callback.return_value callback.assert_called_with() def test_on_start(self): self.conn._on_start(3, 4, {'foo': 'bar'}, b'x y z AMQPLAIN PLAIN', 'en_US en_GB') assert self.conn.version_major == 3 assert self.conn.version_minor == 4 assert self.conn.server_properties == {'foo': 'bar'} assert self.conn.mechanisms == [b'x', b'y', b'z', b'AMQPLAIN', b'PLAIN'] assert self.conn.locales == ['en_US', 'en_GB'] self.conn.send_method.assert_called_with( spec.Connection.StartOk, 'FsSs', ( self.conn.client_properties, b'AMQPLAIN', self.conn.authentication[0].start(self.conn), self.conn.locale, ), ) def test_on_start_string_mechanisms(self): self.conn._on_start(3, 4, {'foo': 'bar'}, 'x y z AMQPLAIN PLAIN', 'en_US en_GB') assert self.conn.version_major == 3 assert self.conn.version_minor == 4 assert self.conn.server_properties == {'foo': 'bar'} assert self.conn.mechanisms == [b'x', b'y', b'z', b'AMQPLAIN', b'PLAIN'] assert self.conn.locales == ['en_US', 'en_GB'] self.conn.send_method.assert_called_with( spec.Connection.StartOk, 'FsSs', ( self.conn.client_properties, b'AMQPLAIN', self.conn.authentication[0].start(self.conn), self.conn.locale, ), ) def test_missing_credentials(self): with pytest.raises(ValueError): self.conn = Connection(userid=None, password=None) with pytest.raises(ValueError): self.conn = Connection(password=None) def test_mechanism_mismatch(self): with pytest.raises(ConnectionError): self.conn._on_start(3, 4, {'foo': 'bar'}, b'x y z', 'en_US en_GB') def test_login_method_response(self): # An old way of doing things.: login_method, login_response = b'foo', b'bar' with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") self.conn = Connection(login_method=login_method, login_response=login_response) self.conn.send_method = Mock(name='send_method') self.conn._on_start(3, 4, {'foo': 'bar'}, login_method, 'en_US en_GB') assert len(w) == 1 assert issubclass(w[0].category, DeprecationWarning) self.conn.send_method.assert_called_with( spec.Connection.StartOk, 'FsSs', ( self.conn.client_properties, login_method, login_response, self.conn.locale, ), ) def test_on_start__consumer_cancel_notify(self): self.conn._on_start( 3, 4, {'capabilities': {'consumer_cancel_notify': 1}}, b'AMQPLAIN', '', ) cap = self.conn.client_properties['capabilities'] assert cap['consumer_cancel_notify'] def test_on_start__connection_blocked(self): self.conn._on_start( 3, 4, {'capabilities': {'connection.blocked': 1}}, b'AMQPLAIN', '', ) cap = self.conn.client_properties['capabilities'] assert cap['connection.blocked'] def test_on_start__authentication_failure_close(self): self.conn._on_start( 3, 4, {'capabilities': {'authentication_failure_close': 1}}, b'AMQPLAIN', '', ) cap = self.conn.client_properties['capabilities'] assert cap['authentication_failure_close'] def test_on_start__authentication_failure_close__disabled(self): self.conn._on_start( 3, 4, {'capabilities': {}}, b'AMQPLAIN', '', ) assert 'capabilities' not in self.conn.client_properties def test_on_secure(self): self.conn._on_secure('vfz') def test_on_tune(self): self.conn.client_heartbeat = 16 self.conn._on_tune(345, 16, 10) assert self.conn.channel_max == 345 assert self.conn.frame_max == 16 assert self.conn.server_heartbeat == 10 assert self.conn.heartbeat == 10 self.conn.send_method.assert_called_with( spec.Connection.TuneOk, 'BlB', ( self.conn.channel_max, self.conn.frame_max, self.conn.heartbeat, ), callback=self.conn._on_tune_sent, ) def test_on_tune__client_heartbeat_disabled(self): self.conn.client_heartbeat = 0 self.conn._on_tune(345, 16, 10) assert self.conn.heartbeat == 0 def test_on_tune_sent(self): self.conn._on_tune_sent() self.conn.send_method.assert_called_with( spec.Connection.Open, 'ssb', (self.conn.virtual_host, '', False), ) def test_on_open_ok(self): self.conn.on_open = Mock(name='on_open') self.conn._on_open_ok() assert self.conn._handshake_complete self.conn.on_open.assert_called_with(self.conn) def test_connected(self): self.conn.transport.connected = False assert not self.conn.connected self.conn.transport.connected = True assert self.conn.connected self.conn.transport = None assert not self.conn.connected def test_collect(self): channels = self.conn.channels = { 0: self.conn, 1: Mock(name='c1'), 2: Mock(name='c2'), } transport = self.conn.transport self.conn.collect() transport.close.assert_called_with() for i, channel in items(channels): if i: channel.collect.assert_called_with() def test_collect__channel_raises_socket_error(self): self.conn.channels = self.conn.channels = {1: Mock(name='c1')} self.conn.channels[1].collect.side_effect = socket.error() self.conn.collect() def test_collect_no_transport(self): self.conn = Connection() self.conn.connect = Mock(name='connect') assert not self.conn.connected self.conn.collect() assert not self.conn.connect.called def test_collect_again(self): self.conn = Connection() self.conn.collect() self.conn.collect() def test_get_free_channel_id__raises_IndexError(self): self.conn._avail_channel_ids = [] with pytest.raises(ResourceError): self.conn._get_free_channel_id() def test_claim_channel_id(self): self.conn._claim_channel_id(30) with pytest.raises(ConnectionError): self.conn._claim_channel_id(30) def test_channel(self): callback = Mock(name='callback') c = self.conn.channel(3, callback) self.conn.Channel.assert_called_with(self.conn, 3, on_open=callback) c2 = self.conn.channel(3, callback) assert c2 is c def test_is_alive(self): with pytest.raises(NotImplementedError): self.conn.is_alive() def test_drain_events(self): self.conn.blocking_read = Mock(name='blocking_read') self.conn.drain_events(30) self.conn.blocking_read.assert_called_with(30) def test_blocking_read__no_timeout(self): self.conn.on_inbound_frame = Mock(name='on_inbound_frame') self.conn.transport.having_timeout = ContextMock() ret = self.conn.blocking_read(None) self.conn.transport.read_frame.assert_called_with() self.conn.on_inbound_frame.assert_called_with( self.conn.transport.read_frame(), ) assert ret is self.conn.on_inbound_frame() def test_blocking_read__timeout(self): self.conn.transport = TCPTransport('localhost:5672') sock = self.conn.transport.sock = Mock(name='sock') sock.gettimeout.return_value = 1 self.conn.transport.read_frame = Mock(name='read_frame') self.conn.on_inbound_frame = Mock(name='on_inbound_frame') self.conn.blocking_read(3) sock.gettimeout.assert_called_with() sock.settimeout.assert_has_calls([call(3), call(1)]) self.conn.transport.read_frame.assert_called_with() self.conn.on_inbound_frame.assert_called_with( self.conn.transport.read_frame(), ) sock.gettimeout.return_value = 3 self.conn.blocking_read(3) def test_blocking_read__SSLError(self): self.conn.on_inbound_frame = Mock(name='on_inbound_frame') self.conn.transport = TCPTransport('localhost:5672') sock = self.conn.transport.sock = Mock(name='sock') sock.gettimeout.return_value = 1 self.conn.transport.read_frame = Mock(name='read_frame') self.conn.transport.read_frame.side_effect = SSLError( 'operation timed out') with pytest.raises(socket.timeout): self.conn.blocking_read(3) self.conn.transport.read_frame.side_effect = SSLError( 'The operation did not complete foo bar') with pytest.raises(socket.timeout): self.conn.blocking_read(3) self.conn.transport.read_frame.side_effect = SSLError( 'oh noes') with pytest.raises(SSLError): self.conn.blocking_read(3) def test_on_inbound_method(self): self.conn.channels[1] = self.conn.channel(1) self.conn.on_inbound_method(1, (50, 60), 'payload', 'content') self.conn.channels[1].dispatch_method.assert_called_with( (50, 60), 'payload', 'content', ) def test_close(self): self.conn.close(reply_text='foo', method_sig=spec.Channel.Open) self.conn.send_method.assert_called_with( spec.Connection.Close, 'BsBB', (0, 'foo', spec.Channel.Open[0], spec.Channel.Open[1]), wait=spec.Connection.CloseOk, ) def test_close__already_closed(self): self.conn.transport = None self.conn.close() def test_on_close(self): self.conn._x_close_ok = Mock(name='_x_close_ok') with pytest.raises(NotFound): self.conn._on_close(404, 'bah not found', 50, 60) def test_x_close_ok(self): self.conn._x_close_ok() self.conn.send_method.assert_called_with( spec.Connection.CloseOk, callback=self.conn._on_close_ok, ) def test_on_close_ok(self): self.conn.collect = Mock(name='collect') self.conn._on_close_ok() self.conn.collect.assert_called_with() def test_on_blocked(self): self.conn._on_blocked() self.conn.on_blocked = Mock(name='on_blocked') self.conn._on_blocked() self.conn.on_blocked.assert_called_with( 'connection blocked, see broker logs') def test_on_unblocked(self): self.conn._on_unblocked() self.conn.on_unblocked = Mock(name='on_unblocked') self.conn._on_unblocked() self.conn.on_unblocked.assert_called_with() def test_send_heartbeat(self): self.conn.send_heartbeat() self.conn.frame_writer.assert_called_with( 8, 0, None, None, None, ) def test_heartbeat_tick__no_heartbeat(self): self.conn.heartbeat = 0 self.conn.heartbeat_tick() def test_heartbeat_tick(self): self.conn.heartbeat = 3 self.conn.heartbeat_tick() self.conn.bytes_sent = 3124 self.conn.bytes_recv = 123 self.conn.heartbeat_tick() self.conn.last_heartbeat_received -= 1000 self.conn.last_heartbeat_sent -= 1000 with pytest.raises(ConnectionError): self.conn.heartbeat_tick() def test_server_capabilities(self): self.conn.server_properties['capabilities'] = {'foo': 1} assert self.conn.server_capabilities == {'foo': 1} amqp-2.2.2/t/unit/test_channel.py0000644000175000017500000003313513131450043016643 0ustar omeromer00000000000000from __future__ import absolute_import, unicode_literals import pytest from case import ContextMock, Mock, patch from amqp import spec from amqp.channel import Channel from amqp.exceptions import ConsumerCancelled, NotFound class test_Channel: @pytest.fixture(autouse=True) def setup_conn(self): self.conn = Mock(name='connection') self.conn.channels = {} self.conn._get_free_channel_id.return_value = 2 self.c = Channel(self.conn, 1) self.c.send_method = Mock(name='send_method') def test_init_confirm_enabled(self): self.conn.confirm_publish = True c = Channel(self.conn, 2) assert c.basic_publish == c.basic_publish_confirm def test_init_confirm_disabled(self): self.conn.confirm_publish = False c = Channel(self.conn, 2) assert c.basic_publish == c._basic_publish def test_init_auto_channel(self): c = Channel(self.conn, None) self.conn._get_free_channel_id.assert_called_with() assert c.channel_id is self.conn._get_free_channel_id() def test_init_explicit_channel(self): Channel(self.conn, 3) self.conn._claim_channel_id.assert_called_with(3) def test_then(self): self.c.on_open = Mock(name='on_open') on_success = Mock(name='on_success') on_error = Mock(name='on_error') self.c.then(on_success, on_error) self.c.on_open.then.assert_called_with(on_success, on_error) def test_collect(self): self.c.callbacks[(50, 61)] = Mock() self.c.cancel_callbacks['foo'] = Mock() self.c.events['bar'].add(Mock()) self.c.no_ack_consumers.add('foo') self.c.collect() assert not self.c.callbacks assert not self.c.cancel_callbacks assert not self.c.events assert not self.c.no_ack_consumers assert not self.c.is_open self.c.collect() def test_do_revive(self): self.c.open = Mock(name='open') self.c.is_open = True self.c._do_revive() assert not self.c.is_open self.c.open.assert_called_with() def test_close__not_open(self): self.c.is_open = False self.c.close() def test_close__no_connection(self): self.c.connection = None self.c.close() def test_close(self): self.c.is_open = True self.c.close(30, 'text', spec.Queue.Declare) self.c.send_method.assert_called_with( spec.Channel.Close, 'BsBB', (30, 'text', spec.Queue.Declare[0], spec.Queue.Declare[1]), wait=spec.Channel.CloseOk, ) assert self.c.connection is None def test_on_close(self): self.c._do_revive = Mock(name='_do_revive') with pytest.raises(NotFound): self.c._on_close(404, 'text', 50, 61) self.c.send_method.assert_called_with(spec.Channel.CloseOk) self.c._do_revive.assert_called_with() def test_on_close_ok(self): self.c.collect = Mock(name='collect') self.c._on_close_ok() self.c.collect.assert_called_with() def test_flow(self): self.c.flow(0) self.c.send_method.assert_called_with( spec.Channel.Flow, 'b', (0,), wait=spec.Channel.FlowOk, ) def test_on_flow(self): self.c._x_flow_ok = Mock(name='_x_flow_ok') self.c._on_flow(0) assert not self.c.active self.c._x_flow_ok.assert_called_with(0) def test_x_flow_ok(self): self.c._x_flow_ok(1) self.c.send_method.assert_called_with(spec.Channel.FlowOk, 'b', (1,)) def test_open(self): self.c.is_open = True self.c.open() self.c.is_open = False self.c.open() self.c.send_method.assert_called_with( spec.Channel.Open, 's', ('',), wait=spec.Channel.OpenOk, ) def test_on_open_ok(self): self.c.on_open = Mock(name='on_open') self.c.is_open = False self.c._on_open_ok() assert self.c.is_open self.c.on_open.assert_called_with(self.c) def test_exchange_declare(self): self.c.exchange_declare( 'foo', 'direct', False, True, auto_delete=False, nowait=False, arguments={'x': 1}, ) self.c.send_method.assert_called_with( spec.Exchange.Declare, 'BssbbbbbF', (0, 'foo', 'direct', False, True, False, False, False, {'x': 1}), wait=spec.Exchange.DeclareOk, ) @patch('amqp.channel.warn') def test_exchange_declare__auto_delete(self, warn): self.c.exchange_declare( 'foo', 'direct', False, True, auto_delete=True, nowait=False, arguments={'x': 1}, ) warn.assert_called() def test_exchange_delete(self): self.c.exchange_delete('foo') self.c.send_method.assert_called_with( spec.Exchange.Delete, 'Bsbb', (0, 'foo', False, False), wait=spec.Exchange.DeleteOk, ) def test_exchange_bind(self): self.c.exchange_bind('dest', 'source', 'rkey', arguments={'x': 1}) self.c.send_method.assert_called_with( spec.Exchange.Bind, 'BsssbF', (0, 'dest', 'source', 'rkey', False, {'x': 1}), wait=spec.Exchange.BindOk, ) def test_exchange_unbind(self): self.c.exchange_unbind('dest', 'source', 'rkey', arguments={'x': 1}) self.c.send_method.assert_called_with( spec.Exchange.Unbind, 'BsssbF', (0, 'dest', 'source', 'rkey', False, {'x': 1}), wait=spec.Exchange.UnbindOk, ) def test_queue_bind(self): self.c.queue_bind('q', 'ex', 'rkey', arguments={'x': 1}) self.c.send_method.assert_called_with( spec.Queue.Bind, 'BsssbF', (0, 'q', 'ex', 'rkey', False, {'x': 1}), wait=spec.Queue.BindOk, ) def test_queue_unbind(self): self.c.queue_unbind('q', 'ex', 'rkey', arguments={'x': 1}) self.c.send_method.assert_called_with( spec.Queue.Unbind, 'BsssF', (0, 'q', 'ex', 'rkey', {'x': 1}), wait=spec.Queue.UnbindOk, ) def test_queue_declare(self): self.c.queue_declare('q', False, True, False, False, True, {'x': 1}) self.c.send_method.assert_called_with( spec.Queue.Declare, 'BsbbbbbF', (0, 'q', False, True, False, False, True, {'x': 1}), ) def test_queue_declare__sync(self): self.c.wait = Mock(name='wait') self.c.wait.return_value = ('name', 123, 45) ret = self.c.queue_declare( 'q', False, True, False, False, False, {'x': 1}, ) self.c.send_method.assert_called_with( spec.Queue.Declare, 'BsbbbbbF', (0, 'q', False, True, False, False, False, {'x': 1}), ) assert ret.queue == 'name' assert ret.message_count == 123 assert ret.consumer_count == 45 self.c.wait.assert_called_with( spec.Queue.DeclareOk, returns_tuple=True) def test_queue_delete(self): self.c.queue_delete('q') self.c.send_method.assert_called_with( spec.Queue.Delete, 'Bsbbb', (0, 'q', False, False, False), wait=spec.Queue.DeleteOk, ) def test_queue_purge(self): self.c.queue_purge('q') self.c.send_method.assert_called_with( spec.Queue.Purge, 'Bsb', (0, 'q', False), wait=spec.Queue.PurgeOk, ) def test_basic_ack(self): self.c.basic_ack(123, multiple=1) self.c.send_method.assert_called_with( spec.Basic.Ack, 'Lb', (123, 1), ) def test_basic_cancel(self): self.c.basic_cancel(123) self.c.send_method.assert_called_with( spec.Basic.Cancel, 'sb', (123, False), wait=spec.Basic.CancelOk, ) self.c.connection = None self.c.basic_cancel(123) def test_on_basic_cancel(self): self.c._remove_tag = Mock(name='_remove_tag') self.c._on_basic_cancel(123) self.c._remove_tag.return_value.assert_called_with(123) self.c._remove_tag.return_value = None with pytest.raises(ConsumerCancelled): self.c._on_basic_cancel(123) def test_on_basic_cancel_ok(self): self.c._remove_tag = Mock(name='remove_tag') self.c._on_basic_cancel_ok(123) self.c._remove_tag.assert_called_with(123) def test_remove_tag(self): self.c.callbacks[123] = Mock() p = self.c.cancel_callbacks[123] = Mock() assert self.c._remove_tag(123) is p assert 123 not in self.c.callbacks assert 123 not in self.c.cancel_callbacks def test_basic_consume(self): callback = Mock() on_cancel = Mock() self.c.basic_consume( 'q', 123, arguments={'x': 1}, callback=callback, on_cancel=on_cancel, ) self.c.send_method.assert_called_with( spec.Basic.Consume, 'BssbbbbF', (0, 'q', 123, False, False, False, False, {'x': 1}), wait=spec.Basic.ConsumeOk, ) assert self.c.callbacks[123] is callback assert self.c.cancel_callbacks[123] is on_cancel def test_basic_consume__no_ack(self): self.c.basic_consume( 'q', 123, arguments={'x': 1}, no_ack=True, ) assert 123 in self.c.no_ack_consumers def test_on_basic_deliver(self): msg = Mock() self.c._on_basic_deliver(123, '321', False, 'ex', 'rkey', msg) callback = self.c.callbacks[123] = Mock(name='cb') self.c._on_basic_deliver(123, '321', False, 'ex', 'rkey', msg) callback.assert_called_with(msg) def test_basic_get(self): self.c._on_get_empty = Mock() self.c._on_get_ok = Mock() self.c.send_method.return_value = ('cluster_id',) self.c.basic_get('q') self.c.send_method.assert_called_with( spec.Basic.Get, 'Bsb', (0, 'q', False), wait=[spec.Basic.GetOk, spec.Basic.GetEmpty], returns_tuple=True, ) self.c._on_get_empty.assert_called_with('cluster_id') self.c.send_method.return_value = ( 'dtag', 'redelivered', 'ex', 'rkey', 'mcount', 'msg', ) self.c.basic_get('q') self.c._on_get_ok.assert_called_with( 'dtag', 'redelivered', 'ex', 'rkey', 'mcount', 'msg', ) def test_on_get_empty(self): self.c._on_get_empty(1) def test_on_get_ok(self): msg = Mock() m = self.c._on_get_ok( 'dtag', 'redelivered', 'ex', 'rkey', 'mcount', msg, ) assert m is msg def test_basic_publish(self): self.c.connection.transport.having_timeout = ContextMock() self.c._basic_publish('msg', 'ex', 'rkey') self.c.send_method.assert_called_with( spec.Basic.Publish, 'Bssbb', (0, 'ex', 'rkey', False, False), 'msg', ) def test_basic_publish_confirm(self): self.c._confirm_selected = False self.c.confirm_select = Mock(name='confirm_select') self.c._basic_publish = Mock(name='_basic_publish') self.c.wait = Mock(name='wait') ret = self.c.basic_publish_confirm(1, 2, arg=1) self.c.confirm_select.assert_called_with() assert self.c._confirm_selected self.c._basic_publish.assert_called_with(1, 2, arg=1) assert ret is self.c._basic_publish() self.c.wait.assert_called_with(spec.Basic.Ack) self.c.basic_publish_confirm(1, 2, arg=1) def test_basic_qos(self): self.c.basic_qos(0, 123, False) self.c.send_method.assert_called_with( spec.Basic.Qos, 'lBb', (0, 123, False), wait=spec.Basic.QosOk, ) def test_basic_recover(self): self.c.basic_recover(requeue=True) self.c.send_method.assert_called_with( spec.Basic.Recover, 'b', (True,), ) def test_basic_recover_async(self): self.c.basic_recover_async(requeue=True) self.c.send_method.assert_called_with( spec.Basic.RecoverAsync, 'b', (True,), ) def test_basic_reject(self): self.c.basic_reject(123, requeue=True) self.c.send_method.assert_called_with( spec.Basic.Reject, 'Lb', (123, True), ) def test_on_basic_return(self): with pytest.raises(NotFound): self.c._on_basic_return(404, 'text', 'ex', 'rkey', 'msg') @patch('amqp.channel.error_for_code') def test_on_basic_return__handled(self, error_for_code): callback = Mock(name='callback') self.c.events['basic_return'].add(callback) self.c._on_basic_return(404, 'text', 'ex', 'rkey', 'msg') callback.assert_called_with( error_for_code(), 'ex', 'rkey', 'msg', ) def test_tx_commit(self): self.c.tx_commit() self.c.send_method.assert_called_with( spec.Tx.Commit, wait=spec.Tx.CommitOk, ) def test_tx_rollback(self): self.c.tx_rollback() self.c.send_method.assert_called_with( spec.Tx.Rollback, wait=spec.Tx.RollbackOk, ) def test_tx_select(self): self.c.tx_select() self.c.send_method.assert_called_with( spec.Tx.Select, wait=spec.Tx.SelectOk, ) def test_confirm_select(self): self.c.confirm_select() self.c.send_method.assert_called_with( spec.Confirm.Select, 'b', (False,), wait=spec.Confirm.SelectOk, ) def test_on_basic_ack(self): callback = Mock(name='callback') self.c.events['basic_ack'].add(callback) self.c._on_basic_ack(123, True) callback.assert_called_with(123, True) amqp-2.2.2/t/unit/test_basic_message.py0000644000175000017500000000070613131450043020016 0ustar omeromer00000000000000from __future__ import absolute_import, unicode_literals from case import Mock from amqp.basic_message import Message class test_Message: def test_message(self): m = Message( 'foo', channel=Mock(name='channel'), application_headers={'h': 'v'}, ) m.delivery_info = {'delivery_tag': '1234'}, assert m.body == 'foo' assert m.channel assert m.headers == {'h': 'v'} amqp-2.2.2/t/unit/test_abstract_channel.py0000644000175000017500000001044213131450043020522 0ustar omeromer00000000000000from __future__ import absolute_import, unicode_literals import pytest from case import Mock, patch from vine import promise from amqp.abstract_channel import AbstractChannel from amqp.exceptions import AMQPNotImplementedError, RecoverableConnectionError from amqp.serialization import dumps class test_AbstractChannel: class Channel(AbstractChannel): def _setup_listeners(self): pass @pytest.fixture(autouse=True) def setup_conn(self): self.conn = Mock(name='connection') self.conn.channels = {} self.channel_id = 1 self.c = self.Channel(self.conn, self.channel_id) self.method = Mock(name='method') self.content = Mock(name='content') self.content.content_encoding = 'utf-8' self.c._METHODS = {(50, 61): self.method} def test_enter_exit(self): self.c.close = Mock(name='close') with self.c: pass self.c.close.assert_called_with() def test_send_method(self): self.c.send_method((50, 60), 'iB', (30, 0)) self.conn.frame_writer.assert_called_with( 1, self.channel_id, (50, 60), dumps('iB', (30, 0)), None, ) def test_send_method__callback(self): callback = Mock(name='callback') p = promise(callback) self.c.send_method((50, 60), 'iB', (30, 0), callback=p) callback.assert_called_with() def test_send_method__wait(self): self.c.wait = Mock(name='wait') self.c.send_method((50, 60), 'iB', (30, 0), wait=(50, 61)) self.c.wait.assert_called_with((50, 61), returns_tuple=False) def test_send_method__no_connection(self): self.c.connection = None with pytest.raises(RecoverableConnectionError): self.c.send_method((50, 60)) def test_close(self): with pytest.raises(NotImplementedError): self.c.close() @patch('amqp.abstract_channel.ensure_promise') def test_wait(self, ensure_promise): p = ensure_promise.return_value p.ready = False def on_drain(*args, **kwargs): p.ready = True self.conn.drain_events.side_effect = on_drain p.value = (1,), {'arg': 2} self.c.wait((50, 61), timeout=1) self.conn.drain_events.assert_called_with(timeout=1) prev = self.c._pending[(50, 61)] = Mock(name='p2') p.value = None self.c.wait([(50, 61)]) assert self.c._pending[(50, 61)] is prev def test_dispatch_method__content_encoding(self): self.c.auto_decode = True self.method.args = None self.c.dispatch_method((50, 61), 'payload', self.content) self.content.body.decode.side_effect = KeyError() self.c.dispatch_method((50, 61), 'payload', self.content) def test_dispatch_method__unknown_method(self): with pytest.raises(AMQPNotImplementedError): self.c.dispatch_method((100, 131), 'payload', self.content) def test_dispatch_method__one_shot(self): self.method.args = None p = self.c._pending[(50, 61)] = Mock(name='oneshot') self.c.dispatch_method((50, 61), 'payload', self.content) p.assert_called_with(self.content) def test_dispatch_method__one_shot_no_content(self): self.method.args = None self.method.content = None p = self.c._pending[(50, 61)] = Mock(name='oneshot') self.c.dispatch_method((50, 61), 'payload', self.content) p.assert_called_with() assert not self.c._pending @patch('amqp.abstract_channel.loads') def test_dispatch_method__listeners(self, loads): loads.return_value = [1, 2, 3], 'foo' p = self.c._callbacks[(50, 61)] = Mock(name='p') self.c.dispatch_method((50, 61), 'payload', self.content) p.assert_called_with(1, 2, 3, self.content) @patch('amqp.abstract_channel.loads') def test_dispatch_method__listeners_and_one_shot(self, loads): loads.return_value = [1, 2, 3], 'foo' p1 = self.c._callbacks[(50, 61)] = Mock(name='p') p2 = self.c._pending[(50, 61)] = Mock(name='oneshot') self.c.dispatch_method((50, 61), 'payload', self.content) p1.assert_called_with(1, 2, 3, self.content) p2.assert_called_with(1, 2, 3, self.content) assert not self.c._pending assert self.c._callbacks[(50, 61)] amqp-2.2.2/t/__init__.py0000644000175000017500000000000013131450043014735 0ustar omeromer00000000000000amqp-2.2.2/requirements/0000755000175000017500000000000013156466127015137 5ustar omeromer00000000000000amqp-2.2.2/requirements/default.txt0000644000175000017500000000001413131450043017276 0ustar omeromer00000000000000vine>=1.1.3 amqp-2.2.2/requirements/test-ci.txt0000644000175000017500000000002313131450043017222 0ustar omeromer00000000000000pytest-cov codecov amqp-2.2.2/requirements/pkgutils.txt0000644000175000017500000000016713154420373017535 0ustar omeromer00000000000000setuptools>=20.6.7 wheel>=0.29.0 flake8>=2.5.4 flakeplus>=1.1 tox>=2.3.1 sphinx2rst>=1.0 bumpversion pydocstyle==1.1.1 amqp-2.2.2/requirements/test.txt0000644000175000017500000000003013132055731016635 0ustar omeromer00000000000000case>=1.3.1 pytest>=3.0 amqp-2.2.2/requirements/docs.txt0000644000175000017500000000002313131450043016602 0ustar omeromer00000000000000sphinx_celery>=1.1 amqp-2.2.2/setup.py0000644000175000017500000000607413131450043014114 0ustar omeromer00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import os import re import sys import codecs import setuptools import setuptools.command.test if sys.version_info < (2, 7): raise Exception('amqp requires Python 2.7 or higher.') NAME = 'amqp' # -*- Classifiers -*- classes = """ Development Status :: 5 - Production/Stable Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 License :: OSI Approved :: BSD License Intended Audience :: Developers Operating System :: OS Independent """ classifiers = [s.strip() for s in classes.split('\n') if s] # -*- Distribution Meta -*- re_meta = re.compile(r'__(\w+?)__\s*=\s*(.*)') re_doc = re.compile(r'^"""(.+?)"""') def add_default(m): attr_name, attr_value = m.groups() return ((attr_name, attr_value.strip("\"'")),) def add_doc(m): return (('doc', m.groups()[0]),) pats = {re_meta: add_default, 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): req = filter(None, [strip_comments(l) for l in open( os.path.join(os.getcwd(), 'requirements', f)).readlines()]) # filter returns filter object(iterator) in Python 3, # but a list in Python 2.7, so make sure it returns a list. return list(req) # -*- 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' # -*- %%% -*- class pytest(setuptools.command.test.test): user_options = [('pytest-args=', 'a', 'Arguments to pass to py.test')] def initialize_options(self): setuptools.command.test.test.initialize_options(self) self.pytest_args = [] def run_tests(self): import pytest sys.exit(pytest.main(self.pytest_args)) setuptools.setup( name=NAME, packages=setuptools.find_packages(exclude=['ez_setup', 't', 't.*']), long_description=long_description, version=meta['version'], description=meta['doc'], keywords='amqp rabbitmq cloudamqp messaging', author=meta['author'], author_email=meta['contact'], maintainer=meta['maintainer'], url=meta['homepage'], platforms=['any'], license='BSD', classifiers=classifiers, install_requires=reqs('default.txt'), tests_require=reqs('test.txt'), cmdclass={'test': pytest}, zip_safe=False, ) amqp-2.2.2/setup.cfg0000644000175000017500000000036113156466127014235 0ustar omeromer00000000000000[tool:pytest] testpaths = t/unit/ python_classes = test_* [bdist_rpm] requires = vine [flake8] ignore = N806, N802, N801, N803 [pep257] ignore = D102,D104,D203,D105,D213 [bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 amqp-2.2.2/PKG-INFO0000644000175000017500000001557513156466127013526 0ustar omeromer00000000000000Metadata-Version: 1.1 Name: amqp Version: 2.2.2 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: BSD Description-Content-Type: UNKNOWN Description: ===================================================================== Python AMQP 0.9.1 client library ===================================================================== |build-status| |coverage| |license| |wheel| |pyversion| |pyimp| :Version: 2.2.2 :Web: https://amqp.readthedocs.io/ :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: https://kombu.readthedocs.io/ .. _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)``. - Authentication Failure Notifications Instead of just closing the connection abruptly on invalid credentials, py-amqp will raise an ``AccessRefused`` error when connected to rabbitmq-server 3.2.0 or greater. - 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 .. |build-status| image:: https://secure.travis-ci.org/celery/py-amqp.png?branch=master :alt: Build status :target: https://travis-ci.org/celery/py-amqp .. |coverage| image:: https://codecov.io/github/celery/py-amqp/coverage.svg?branch=master :target: https://codecov.io/github/celery/py-amqp?branch=master .. |license| image:: https://img.shields.io/pypi/l/amqp.svg :alt: BSD License :target: https://opensource.org/licenses/BSD-3-Clause .. |wheel| image:: https://img.shields.io/pypi/wheel/amqp.svg :alt: Python AMQP can be installed via wheel :target: http://pypi.python.org/pypi/amqp/ .. |pyversion| image:: https://img.shields.io/pypi/pyversions/amqp.svg :alt: Supported Python versions. :target: http://pypi.python.org/pypi/amqp/ .. |pyimp| image:: https://img.shields.io/pypi/implementation/amqp.svg :alt: Support Python implementations. :target: http://pypi.python.org/pypi/amqp/ Keywords: amqp rabbitmq cloudamqp messaging Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: License :: OSI Approved :: BSD License Classifier: Intended Audience :: Developers Classifier: Operating System :: OS Independent amqp-2.2.2/LICENSE0000644000175000017500000000450413131450043013403 0ustar omeromer00000000000000Copyright (c) 2015-2016 Ask Solem & contributors. All rights reserved. Copyright (c) 2012-2014 GoPivotal, Inc. All rights reserved. Copyright (c) 2009, 2010, 2011, 2012 Ask Solem, and individual contributors. All rights reserved. Copyright (C) 2007-2008 Barry Pederson . All rights reserved. py-amqp is licensed under The BSD License (3 Clause, also known as the new BSD license). The license is an OSI approved Open Source license and is GPL-compatible(1). The license text can also be found here: http://www.opensource.org/licenses/BSD-3-Clause License ======= Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Ask Solem, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Ask Solem OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Footnotes ========= (1) A GPL-compatible license makes it possible to combine Celery with other software that is released under the GPL, it does not mean that we're distributing Celery under the GPL license. The BSD license, unlike the GPL, let you distribute a modified version without making your changes open source. amqp-2.2.2/Changelog0000644000175000017500000005347113156464717014243 0ustar omeromer00000000000000Changes ======= 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-2.2.2: 2.2.2 ===== :release-date: 2017-09-14 09:00 A.M UTC+2 :release-by: Omer Katz - Sending empty messages no longer hangs. Instead an empty message is sent correctly.(addresses #151) Fix contributed by **Christian Blades** - Fixed compatibility issues in UTF-8 encoding behavior between Py2/Py3 (#164) Fix contributed by **Tyler James Harden** .. _version-2.2.1: 2.2.1 ===== :release-date: 2017-07-14 09:00 A.M UTC+2 :release-by: Omer Katz - Fix implicit conversion from bytes to string on the connection object. (Issue #155) This issue has caused Celery to crash on connection to RabbitMQ. Fix contributed by **Omer Katz** .. _version-2.2.0: 2.2.0 ===== :release-date: 2017-07-12 10:00 A.M UTC+2 :release-by: Ask Solem - Fix random delays in task execution. This is a bug that caused performance issues due to polling timeouts that occur when receiving incomplete AMQP frames. (Issues #3978 #3737 #3814) Fix contributed by **Robert Kopaczewski** - Calling ``conn.collect()`` multiple times will no longer raise an ``AttributeError`` when no channels exist. Fix contributed by **Gord Chung** - Fix compatibility code for Python 2.7.6. Fix contributed by **Jonathan Schuff** - When running in Windows, py-amqp will no longer use the unsupported TCP option TCP_MAXSEG. Fix contributed by **Tony Breeds** - Added support for setting the SNI hostname header. The SSL protocol version is now set to SSLv23 Contributed by **Dhananjay Sathe** - Authentication mechanisms were refactored to be more modular. GSSAPI authentication is now supported. Contributed by **Alexander Dutton** - Do not reconnect on collect. Fix contributed by **Gord Chung** .. _version-2.1.4: 2.1.4 ===== :release-date: 2016-12-14 03:40 P.M PST :release-by: Ask Solem - Removes byte string comparison warnings when running under ``python -b``. Fix contributed by **Jon Dufresne**. - Linux version parsing broke when the version included a '+' character (Issue #119). - Now sets default TCP settings for platforms that support them (e.g. Linux). +----------------------+---------------+ | Constant | Value | +======================+===============+ | ``TCP_KEEPIDLE`` | ``60`` | +----------------------+---------------+ | ``TCP_KEEPINTVL`` | ``10`` | +----------------------+---------------+ | ``TCP_KEEPCNT`` | ``9`` | +----------------------+---------------+ | ``TCP_USER_TIMEOUT`` | ``1000`` (1s) | +----------------------+---------------+ This will help detecting the socket being closed earlier, which is very important in failover and load balancing scenarios. .. _version-2.1.3: 2.1.3 ===== :release-date: 2016-12-07 06:00 P.M PST :release-by: Ask Solem - Fixes compatibility with Python 2.7.5 and below (Issue #107). .. _version-2.1.2: 2.1.2 ===== :release-date: 2016-12-07 02:00 P.M PST - Linux: Now sets the :data:`~socket.TCP_USER_TIMEOUT` flag if available for better failed connection detection. Contributed by **Jelte Fennema**. The timeout is set to the ``connect_timeout`` value by default, but can also be specified by using the ``socket_settings`` argument to :class:`~amqp.Connection`: .. code-block:: python from amqp import Connection from amqp.platform import TCP_USER_TIMEOUT conn = Connection(socket_settings={ TCP_USER_TIMEOUT: int(60 * 1000), # six minutes in ms. }) When using :pypi:`Kombu` this can be specified as part of the ``transport_options``: .. code-block:: python from amqp.platform import TCP_USER_TIMEOUT from kombu import Connection conn = Connection(transport_options={ 'socket_settings': { TCP_USER_TIMEOUT: int(60 * 1000), # six minutes in ms. }, }) Or when using :pypi:`Celery` it can be specified using the ``broker_transport_options`` setting: .. code-block:: python from amqp.platform import TCP_USER_TIMEOUT from celery import Celery app = Celery() app.conf.update( broker_transport_options={ TCP_USER_TIMEOUT: int(60 * 1000), # six minutes in ms. } ) - Python compatibility: Fixed compatibility when using the python ``-b`` flag. Fix contributed by Jon Dufresne. .. _version-2.1.1: 2.1.1 ===== :release-date: 2016-10-13 06:36 P.M PDT :release-by: Ask Solem .. _version-2.1.0: - **Requirements** - Now depends on :ref:`Vine 1.1.3 `. - Frame writer: Account for overhead when calculating frame size. The client would crash if the message was within a certain size. - Fixed struct unicode problems (#108) * Standardize pack invocations on bytestrings. * Leave some literals as strings to enable interpolation. * Fix flake8 fail. Fix contributed by **Brendan Smithyman**. 2.1.0 ===== :release-date: 2016-09-07 04:23 P.M PDT :release-by: Ask Solem - **Requirements** - Now depends on :ref:`Vine 1.1.2 `. - Now licensed under the BSD license! Thanks to Barry Pederson for approving the license change, which unifies the license used across all projects in the Celery organization. - Datetimes in method frame arguments are now handled properly. - Fixed compatibility with Python <= 2.7.6 - Frame_writer is no longer a generator, which should solve a rare "generator already executing" error (Issue #103). .. _version-2.0.3: 2.0.3 ===== :release-date: 2016-07-11 08:00 P.M PDT :release-by: Ask Solem - SSLTransport: Fixed crash "no attribute sslopts" when ``ssl=True`` (Issue #100). - Fixed incompatible argument spec for ``Connection.Close`` (Issue #45). This caused the RabbitMQ server to raise an exception (INTERNAL ERROR). - Transport: No longer implements `__del__` to make sure gc can collect connections. It's the responsibility of the caller to close connections, this was simply a relic from the amqplib library. .. _version-2.0.2: 2.0.2 ===== :release-date: 2016-06-10 5:40 P.M PDT :release-by: Ask Solem - Python 3: Installation requirements ended up being a generator and crashed setup.py. Fix contributed by ChangBo Guo(gcb). - Python <= 2.7.7: struct.pack arguments cannot be unicode Fix contributed by Alan Justino and Xin Li. - Python 3.4: Fixed use of `bytes % int`. Fix contributed by Alan Justino. - Connection/Transport: Fixed handling of default port. Fix contributed by Quentin Pradet. .. _version-2.0.1: 2.0.1 ===== :release-date: 2016-05-31 6:20 P.M PDT :release-by: Ask Solem - Adds backward compatibility layer for the 1.4 API. Using the connection without calling ``.connect()`` first will now work, but a warning is emitted and the behavior is deprecated and will be removed in version 2.2. - Fixes kombu 3.0/celery 3.1 compatibility (Issue #88). Fix contributed by Bas ten Berge. - Fixed compatibility with Python 2.7.3 (Issue #85) Fix contributed by Bas ten Berge. - Fixed bug where calling drain_events() with a timeout of 0 would actually block until a frame is received. - Documentation moved to http://amqp.readthedocs.io (Issue #89). See https://blog.readthedocs.com/securing-subdomains/ for the reasoning behind this change. Fix contributed by Adam Chainz. .. _version-2.0.0: 2.0.0 ===== :release-date: 2016-05-26 1:44 P.M PDT :release-by: Ask Solem - No longer supports Python 2.6 - You must now call Connection.connect() to establish the connection. The Connection constructor no longer has side effects, so you have to explicitly call connect first. - Library rewritten to anticipate async changes. - Connection now exposes underlying socket options. This change allows to set arbitrary TCP socket options during the creation of the transport. Those values can be set passing a dictionray where the key is the name of the parameter we want to set. The names of the keys are the ones reported above. Contributed by Andrea Rosa, Dallas Marlow and Rongze Zhu. - Additional logging for heartbeats. Contributed by Davanum Srinivas, and Dmitry Mescheryakov. - SSL: Fixes issue with remote connection hanging Fix contributed by Adrien Guinet. - SSL: ``ssl`` dict argument now supports the ``check_hostname`` key (Issue #63). Contributed by Vic Kumar. - Contributions by: Adrien Guinet Andrea Rosa Artyom Koval Corey Farwell Craig Jellick Dallas Marlow Davanum Srinivas Federico Ficarelli Jared Lewis Rémy Greinhofer Rongze Zhu Yury Selivanov Vic Kumar Vladimir Bolshakov :github_user:`lezeroq` .. _version-1.4.9: 1.4.9 ===== :release-date: 2016-01-08 5:50 P.M PST :release-by: Ask Solem - Fixes compatibility with Linux/macOS 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 A.M :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 macOS 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-2.2.2/docs/0000755000175000017500000000000013156466127013344 5ustar omeromer00000000000000amqp-2.2.2/docs/conf.py0000644000175000017500000000143213132054142014623 0ustar omeromer00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals from sphinx_celery import conf globals().update(conf.build_config( 'amqp', __file__, project='py-amqp', description='Python Promises', version_dev='2.3', version_stable='2.2', canonical_url='https://amqp.readthedocs.io', webdomain='celeryproject.org', github_project='celery/py-amqp', author='Ask Solem & contributors', author_name='Ask Solem', copyright='2016', publisher='Celery Project', html_logo='images/celery_128.png', html_favicon='images/favicon.ico', html_prepend_sidebars=['sidebardonations.html'], extra_extensions=[], include_intersphinx={'python', 'sphinx'}, apicheck_package='amqp', apicheck_ignore_modules=['amqp'], )) amqp-2.2.2/docs/images/0000755000175000017500000000000013156466127014611 5ustar omeromer00000000000000amqp-2.2.2/docs/images/celery_128.png0000644000175000017500000006321213131450043017157 0ustar omeromer00000000000000‰PNG  IHDR€€Ã>aËiCCPICC Profilex…TßkÓPþÚe°á‹:g >h‘ndStCœ¶kWºÍZê6·!H›¦m\šÆ$í~°Ù‹o:Åwñ>ù Ùƒo{’ Æaø¬ˆ"Lö"³ž›4M'S¹÷»ßùî9'çä^ ùqZÓ/USOÅÂüÄäßò^C+ühM‹†J&G@Ó²yï³óÆltîoß«þcÕš• ð ¾”5Ä"áY i\ÔtàÖ‰ï15ÂÍLsX§ g8ocáŒ#–f45@š ÂÅB:K¸@8˜iàó ØÎä'&©’.‹<«ER/ådE² öðsƒò_°¨”é›­çmšNÑ|ŠÞ9}pŒæÕÁ?_½A¸pX6ã£5~BÍ$®&½çîti˜íeš—Y)%$¼bT®3liæ ‰šæÓíôP’°Ÿ4¿43YóãíP•ë1ÅõöKFôº½×Û‘“ã5>§)Ö@þ½÷õrŠåy’ðë´Õô[’:VÛÛäͦ#ÃÄwQ?HB‚Žd(à‘B ašcĪøL"J¤ÒitTy²8Ö;(“–íGxÉ_¸^õ[²¸öàûžÝ%׎¼…Å·£ØQíµéº²šua¥£ná7¹å›m« QþŠå±H^eÊO‚Q×u6æS—üu Ï2”î%vX º¬ð^ø*l O…—¿ÔÈÎÞ­Ë€q,>«žSÍÆì%ÒLÒëd¸¿ŠõBÆù1CZ¾$MœŠ9òÚP 'w‚ëæâ\/מ»Ì]áú¹­.r#ŽÂõE|!ð¾3¾>_·oˆa§Û¾Ódë£1Zë»Ó‘º¢±z”Û'ö=Žª²±¾±~V+´¢cjJ³tO%mN—ó“ï„ |ˆ®-‰«bWO+ o™ ^— I¯HÙ.°;í¶SÖ]æi_s9ó*péýÃë.7U^ÀÑs. 3uä °|^,ëÛ<ž·€‘;Ûc­=maº‹>V«Ût.[»«ÕŸÏªÕÝçä x£ü©# Ö¡_2 pHYs  šœ IDATxí½ néYß÷öÞ}»û®sçΦÑ,ÍH#ÍhA ÂaYl±(»›*ÇqÀ$¸’ؘpˆSÁ’”q‚1•ì  %V!$!‰Ñ®Ñhf_ï¾ô½½wç÷û?çí¾3÷~W,&U.ôvß9ç]žýyÞå¼ç|cÛÛÛíËé/¯Æÿò²þeΕÀ— à/¹Lþyù#ýya|¹ýŸ]tá®>üOmQøµ±v_Ûþå¶õç%àÏÎú—[ûö±ñvgÛn?ÖTÇŸÊ Æþ4õQþ8õ·.=y\9 \,”ÿÿÎÕ÷æÅè.§£‹Ë_|þ'2€x=-ÅÆù §_Éçk^ÿ7Ýuý¡›ONNc_Âð³yõìPcÛnC/†Cþ8y6ì#–‹Ïw`¼ðĪ—ÇUõ¾Tù ¡ý)®.B:>>Ö¶v®/æñòðÆ¢Â^'Ðã ’rÚnøb[ß\Ý~æøÇîýõ?þ,™àóT´z±¾È»bú’p±Eqþî7ó?ümïü«÷¼þõw¶ƒGæÚÜÌž611Ù¶· Jbá³³Ú;º¥\޽À\ºêw)¥°Â*m ÉÀX]•2òØ.a 5…¾ bÃÅy»ÅÛ”WÉåË/®Y罞\õ4äy0òB”t#C?ùýº·èÇ‚Rß‘‡íšn¨°œo)ßßLó@´º¶ÒN]n÷~âóíWý7>öá_½ÿç{,¿Xw^_.]Ñ:€±«ÇÚ¹öÏ~ê§ÿþ÷ã7½©Í¬´Ó«G7Ï­œjë[k0,Ç¥V‰4iý¦0ÀéäD ‡Š)êÙN»)o)&µîÝbÄ¢¢K¾2¸jOŒ¶ÞÆ×ÍïÂJÉP ¹IE›¦ó2­@Úë'ú©<ÅKšR«e·¥ÒdnZÐ^#ÂËÅï9H;§©³¹µcðÊ|Ë4qnPЏZk›EŸ¢“6ë6xߤž¸Lõíu¡ivr¶-Ìîoûg¯žØ^n¿ñkoÿåþ3?׿Ûº}t{©ëЗK# €†–mGùGÚ/ýß?óOßõò»o=zâ ÛÇN£ßWIãmzj*a®+\Kš´¬~ñQò»âeÒ²‰‰l¯§Æñx¯ÔÔÄ­¶Û&Âñ:õw<ÅNRŽý WE(9Û+á(ˆ6:9ʰã–iBJ¶U`F…­áX®2Cÿ8øÈ¦ù¶—g†ÄUÖmŠf N:̳MHç xµ9ŽclmrBÚ¤Îç¡-ÕáùX_þ…¡œ= Zøò³±¹Æq«]µïðæ-‡î{ð³ÇÆÿÃü/ÞÛžoß5At$/úúÒ°ìçßó+?ù}7ßµoýS|ŒX?56=9=¹„òe$A*I&&øŠH––¯ì;#“(ØTÖN›´-oµÞÊT!ŒëiêwG‘ÂTX~YøuŸ5Y.=Ö©º¥íO¨¥Pê´i;NËJè¥PK¥¢h*(¶'.XF3Îqck£`Sg ØQÞ +k{ì#FÂ%ø„ïµü‚)4Wy–›Ýi4kŸ«Âøúöë^öÆÇ>{vêÝßöÿÕöéí¿Œ‘Pî'Ä‹õ' Ôß·ÿøOþíï»ñ• [Ÿxø£“[ÓQ¾Œl1øTh›››ùÈz„¯@cùÑ=_‚U •J…p#íëÆâ=ip¨[a[a€|00z8Õ3»b‚Ÿ/ì¹!UÏTÉ Vü^ó±Üsõ,,ák Ö“/ËýÔ¹‹†ÐŸüj³±¾Yxœ–‡`«üÀ5¢)³D1C’ÆD®Åm‰Ê7?´õê¡…J¶¯ˆ â‘¿ôó7ÅXl|{vìÞ?6yÃ+ölýãÿÞï£î·Cƒº,O äݯK €ŠZË&‡¹·¼ëU?òÖoxYûâÓŸÛÛšÓ;dN‹‹ ‚RQ–9Xƒê”P¥\¦¼¶ÜóëV 2I§®¾±­Ç¿ò„-ƒ‚AÁÞPÌê.z¤HªÚ†¦m¤ NO“½õöj¡Idâ±DÙg@K^7•þhù€_ *VØÒçÑD••g–‡~ÚÎuÛ‡*ÚF°½eð €$MÊe}c3Ç.# Ö$+£ÃÚÞœ{àéÏoÕ;^ÞÞò®;D]:-4i]_Šõ…éÝ;“­·}Ý×ßý†•±³íÌÙ%Àïö{i0^vR¸Q2ÈEU­×~&âU€âîŒ×Ѿ;^‚8dSôLÔÎöŸy0Ûáp]øË#…j¹¸loŠr…A{£åÊ·çùPW¦‘ôIu´oÆÛÄÝÒ$­5Ö’áŸ8=Ú´GAi×.4B½u‚.OÞL‘€$ò1G:ÅÑiT¥\¹èx‘¯uÁ8ʤ¡s£‡ÎpêôÒÄòö™öö¿òš7PýmÂj»ºÍ¥_—À»{ÑÁöu·Ü½·:w|s¼M €1*„вäª^ÌIˆ„mè õð*S~Ê y 3‚T0´ÞÀÈxÆ(ÃöÔÛ¤«Q¶0q£,é!wÈ«ò]XÂ4¯”îÀ®ãWÞÖ“V°ø­« 1Pv!k ´` ¸4ª?\#©WòPy[œË?€ø.•§Ñ ;òàhD'i•Fò"? 'õî¡óoÞmF3¤€“ Ú3(#ÆÐÆ&Û©ó'6oE‡ ]ZsG·^ é’¥à_fyË{Ã;nºkva¼]X]¯ª©Å) 1z0™'Q5WWƒ•’_caÛè5^PÁÖïE™“á3 †Y2²I_ªÒ‘—É‚ÏêÔÃÞÀIK§™UJ裱ÂÈ9¨*:ÁY•ípQp€˜2Ë«vûÛâWΔ”òоiJ½®+6üÛ:*µø‚=‚¼ð¯£Øž5¥Äï5z¼)0dк†¯¡¾p’§HÒVÞv®WVÖÚâât{Ã=7ÝQÐj’om'Y)P[›Û·ï~-}uŒ #y?žWx…A˜°OÛ G@Ú¤¿êIf"2œ® Þ0èQåÄÚe( –7Z^I±¨\=Kc€T™%[…¤ý0…Ò8,B´m{Ãs®Â‰j÷t Ï®1Qźé7¡C+"ȹ¼)l*úµÆ ¡U¢Nð—Ñ„–Þe/‹ŠœFùŠ@ &'šŒœ†~N4}ĭׇډ×ùÊZ ˜"CÎ9å'Ùœ´‹õõrÞýû÷í'sNŽJÇB©tIò§‘×Ì6ž·±¾Ê‹!”Á\7ÄQ&T’É)ºŠð:ŠºÓº& •1 Œ‚0'oi“**©$®r,1»„Yp7Q´I 3žé±Ó5´±­ÉÚãóeEY­ýF¹WΠ øN^€W´‘øÍ3íâ—> ùe^…5˜åQˆ°!1Oá Ôxâ'/¼°è$ j•o=£¨†R<’ç¶Q®ÝÔSŒæk7Þ*Àv\¾ŸæsÏ Ò( ¹ÆÂ·H€ ‚Kù„o v’"RªK”i(Çñ–È@å8±nÂðÀ(H†£åL•87œŠÁS·ÉàWÅØÞ¤P*yîG¥ÑÂHž–Y‘ëUÁ:–QàB¯n†ØCdmm¤ÛQíåQ Ê¢0:LÑ~Uªy†ö²ËPEQÖfE$œA”À·QTäþYÏÅ¡r:j_òW¸Q–`}ót’š:‚0p3çb#" !û¹$4ÄjÅNàĂ㲟Dدõ.ƒ¬VF! ²¥'I!q/‘z*·`gOÆøóÝGi«©©›´p§eou>â—FOOS¦¢…ONà9WƒP¬9Þ¦R¿ ¦F)ôÞɳ\øl y¬jz.¼ô"îºÆœÖ)“·Îh`”áfÝ#áP‚Ø.R纎%o)Nhà‚i^8·Ë r·ì86Á¯ö›²ÊXAÂp£°d¤ .M£ @TÀ1†@ÈÀ\1¡‚‹á g1BÀ£ã} ‘›´Qá–k©”°§¸o¡4ö˜%š± cn~¶¼j±MO·= ³¹ž™¦ÜðÜX„Y£íf[ÛXåö+K­tá^ƒöœp½I·aíý5GnÅu‰>üF(ï½ÿÿŒµÒ&hà©xê¤h2øG‚ÒaB²à*µ”®nìJ0NŒ]akÔq7CÅÒHÐóã1k€WFAsN•˜rÂ0R)«çÚéˆ(pñ•*Á”žfL›(Þö&aœ† ÎâÓ¶Æ”ÆÇ1š…ùE®fÚÑgε/|æÁöÙ?z¢=ð…§Ú}÷=ÔÎ<»XÚ¯—½úºväšýíÆ[·ëo¸ªÝpÓÕíšë÷µÃ×ìk{÷-´¹=Åv[ÛÄa3@FÐŒ´cö´„c• ïÁò)£ˆí˜c+¯v)/¥š©#VŒŽzΛjëÈ¿©¤ÀHÎdLJ¬¯,*?q*T£G§Fëñ§Õ ¿F<ÅŠ%Ð.dp­yy± Î ¤cxd)V†!|»}º’Ö¬ÇIM}À¦6m`j †»òËú1$ë(™íɨa~ž3Ûþ±ö›¿ú©öoÿõïµ¥£¥ð=×·¶wq¾:‚qk’1Ëæºc—bZÒ›’€»¾¶•éÙ&½²²ÞέoOÝ÷Lûðo u†Ã‘—îo¯~Í-í寸¾ÝöÊkÛKn9ÔŽ\·¿Íƒk|RïÛhË+Ëm}›‘ø€‘GÑ(åÍð ÿ*Ö1ƒJI>"‘ù®r€hŠÜ7éâ” /Q¸ò6z8÷˜ƒ]4Ĩ[ø(£M˜W0‘yØ(bœ¡[„ãP.«1Ž¢· ïˆS":CⵋÃi„*7fm1ˆqG˜ (êƒÂÕOd·oï><þ|{Ï¿ú@û—?]Zºá•‹íú›ö…EØ4¢”õu”1C»Y`jÍü#¦Ð¹º¶Ù¦èƦQ”ÓZêÏϵÅñŶ¸²Ý¦o¦&ù~ÖYQ¹¿ý‘O¶ßþµOvêÛk¾òæv×ëniwÅÍíÖWÁ öb‹C,//·6jxÃLçØäþƘ3Zo5”ƒìî,oK©Ï$$ÖœŒ±”ë0Ö‚ PlxèÑ6åø%!`Ñ lsUðS𢯑 ²%¨Fü¥D O$P¦(u .È x­®'­¹ææŒðÜ8°Äi<½ ]í3*DùäÇr1x½WêYA¶í[ÜÛ>þÁGÚõ¿ÐN>u¡ÝúÚ«ðpM‚cl½Mчs3 bð|„=—MM×®Ë*‚î8ž?37ÞÐQ Ü‘îËò2>µ…q°¶ŠÒ …áEŸ™i·`å–šU,Ÿ_oO<ûhûôÿòh禽ý]wµ{¾êŽöª×ÝØn¼õP;¸w±­­­¶•Õ mcl Óä®!±•‡rh©Œ»rÊkaÛ––ê󭳫 #Šeʲdn¹™|8-cenæÒHHèE9Ø„‰#@µPJ$"F öNÈ¡ݦ?‡:·žm†hírx'ëäöm긓jší‡“ôóû´÷ý_Ô~ôïýR;ò²=íŽ{®Æ{ë®áì•ÌfFE4šÁ›7·ð~ Á Û™™²¹Ádr–î„2…6K”Èš;¸6ô&Y‡ ’8\_TSwyi=d–ˆ1·÷P»ê0Æö»¿ýÙö»ïuk^koùúW´·}ëÛë‰×ßpˆè¹Õί°³¹ŒÒW#Ì!:K´Ä³U*§ÙyG4NÝ …Y“ N.ƒEÙé`¹,ëé˜ÑQšy-LÍæòi¤€*-üE9lQŸÖ©h±Ûß:ʹƒ Ç:FtŸ¾È£†•¾ÉÂSÙNÍÀíwù¼09>Õöí;ÐÞûo>ÙþñüR{Ùë·É -Í¡x›Î-¢xŒf¯µo™Gñ(î<^:˹1Ä¥[£ÍžY§…ãméíg§ ™u ò'Pè,i·ïžÃ(&g kÇN'¨Kë4ekkЋa9vÐKÓ^~÷ÁD«åóíÞÏßÏ8âþÈïÛ¾÷Íí¯¼ë5íŽWÞÐö´å‰³íÂ…%â;ÓTp% €‡hA¨NR‰Lbsü¨ÐK~œ+Û"×£r/ÁK—©@F¤‘ b³¬Jà Âá¤BIÌ•ÐêóÇÈ–£L‚Á–Ý…{ÿTÐ Ÿ¤N1a$©UÅb,–jø¥þ8÷³f@‘ì÷÷µýæƒ(ÿÿl·¿ñ0›‘6ÚÞmG²Å;,2ܯÓG.ÌMeöaèžió À€®5®÷,L·)à_X^o ÎÞý5m\g,°¸ošñƒ@Êf1Šù Á?¦{æ§Ãë<vÏÆÞ7‚âFÙê2Qe­f(+«(ÍiòžívÓKµÍkÙ ]¿ò ˜Ï›¾îŽö×þúÚÝoº©í½ê`[:{2÷YTŸÞiÐcÀP|ñdÊ”—³ %æB˜ÇŒò=碶ˆü¡Á<õ¢Ž´d/ÜQi¤Äkâ<:qH¹eަh¤¯<ÙЙ%I*¸še%Û$pÒGº£18È´ÜOºFÛÁÈQcqÏþöèOµü®Ÿm7ÝͽŒ©Í6M7<=;Ö¦0ÆÙyz~pOWAG±Gj‡ (Íð~þìFÛ¿Ž5B´«e¸×Âj²XuöÜJÛ»w&Ôuøs0¶¸·VO³vp€þ~·^[Õ÷ÛÂ>”ok(tŽîcr ±ÉÝÌÌœæHD€ð)ò]‹ˆ¤ˆRÛÌo{ã!46Þ>ùù/¶þÎÛËﺮ½ó;_×¾æ]·Óî‡>G\ïÏ=CšÌ eÌYŒY+¯·®]…Ëé¶Ä`)S¦®ÕH³Î¥nÔ¾ú•ʤG•$¡žòŒü!HË”á̃]ˆ§"€Gë$3aJ‹õRB¨³¡…Ó/1cH Nvìj¦ò|Џ»¾:ÞþÅO¿¿5t?¿wªMãY³{ì!ŠL êÎÍ£ŽNçfŒP(û PÙzòÂÓë Î00:¬^÷àÍD¬eËh3‹Rí2ÒrqþÂáž±Ÿu Áh2O»Ò ÊÞÌ<èYÐ(çézf|rÛEãœYÀH‰ÖÙ_m/½ñ`{ům~ö™öài‹ÓWÇ!d ‹IÊ6òF˜ Ê^ˆ†£ð”+ˆØÄu¨RtÙPäêBú †0 ´WC—O# …Y§q†èMõTÛíLÜ„™%ìÏãi _AÞ'é_ l;­S¡z±¾Eúv¢K‚Äýûæ»ÂŒbq~w²?vjÇX5\ :ø\ÃL;xpŒ5¢ÄTàa–¹¯ LïœGñ®½‹ˆèøaƒ†fTÒTfŒp/]`„œžÁηö­ýëÛòæqè½€«· 0TJ–4 d¨#9äæ˜IdzŠòç蟲äcªÐ_å¶·F©p™/sd à‚¤ôб>³\òfùÔ* sqGój½¬N¢´Né1Ĺέ‚b”Éõ6žãúþÔä$ kþÀ}­aj9Þ•Zc$¼¸8ËÖôŽ'#|Ë ùz²¡}ß¾9H˜nüñ#¹QsŽiøs§”1¦8|h_{ò±§ÛçŸmíðÐC´§Ž·v뵇+ˆR§g«pàð¢ 2h¼ß»o&Š_ç Ùè0;;!X@bMÀzëÐ{~ÉÉT[Ø;×òÉöÍïúºvÛ×¶'O}4Ó½ÍuV Q°’Q]™ëcà.ï:m5);˺’7C©tó\*æ<]pnŒ…nysÌ(aÄN'—¤‘`¿++ÍÌnÀÕ6—1˪5ÈÇu¥‚¼æQb‚‹%Ôˆ6ß !—åe lÂåäÔl;{b½ÝûñûÛ­7ímô¥20M˜Ãs×ðH]5b//ß‹âõB#€iïœ`oÀ“O=ßžfDÿö;ßÖ¾òµ_ßn{Ù­íúë¯ÃøœŠB‚F€'OŸj'Oœj§—N´'Ÿx¦=ôǶçN=Üž9û™öáŸgD×Ú›_Š1N!âLµC‡fXÓÎ^8Q÷†PFò…DP´ë.+é2éX\DÎ]ž:,$YMU”žm+Œ¡´Wê¾ø0ÒâÑâ €‚¯[vQ–¹JÃ`œ&Ñ0æ‚lر3¨Ê†Dºia‚1Áø´dÈðx{ö©“­Ýh£S>úP•¶Bߺ€!ú–ί&ìk L;&X[kŸøÂj{ÿÿöSíÐísOü*pVñ4=Ü.œti¯’Þ§±kçbÐçVžnÏŸñ”µ…q6‰Ìl 3W·—Ý}M{Õë¿¶}Ë·~Cûá?Üf|ñû¿ÿ±öÁ{ßß<ýh[`qx~;rä t­¶³gWÚ…s·¯zç«ÛkßðòöÜÉOEvÛðO…ÿ®§’‡ßÞvW6Ub Í‘/kÄëFìX¡W {Cõ¡²¥þ›2y™4ÒÒ—ÄQ@ûö/¡Ç“†@vnªg ¶ÉÐÎZK,Ó6*ŠáC?Z§7y%Vè éLÏœ>ßYñseájßÁC³mœ¥^GùÞ¸™ãÜØwni…Õ¾Ù ÀÖV'Ûo~úñö?ÿ'?ÚnáñýOý¿ Aóøú»y"iº™íÁ¤ôcŒàĹdÖÛ{P¾£pæ ü–ÚÉ3À¡}Œ®gæPÛ¿pC{í›^ÚÞô–»Ú|ê{Úç?÷PûÐÇþ€Èð‹í7~ç‘vÓKì&îhûü‰öÏ~诵‰=KíôÉc𩱩xy—„¢A…©Ð_ ÞM‘ؤáê”Êή#¢·mºPÅ[œ E9´Ë|4És©Q!­#o°07kTZ(€@‰cī³óVÙAHT*òAT–…¥Š¥RÇ©#²©¤l IDAT¸¦Àr.pŽ=դ硆)nÞô)—Û¼ö1ºžÄð\pLJ¼ÛçȆ{û¯=|u{Û×¾¶=sæ3‚…¿µÅ=_x©mmråTtˆRm–+<·P—wÉ–:k¦Œ¶ÇVÚ™u—[z¼=A×83u XĸówÏ[ÿNûžçþFûä§>ÝÞ÷›ïmÿög«í½…»ƒoe{þôÕ\ œ@¬/•ç˜ÈgÕ©Ø¥)až iŠCJçT[Í|H«gt¾¡ý V}2ëáÄ—¦‘Ë”#B)3+Ài’)1nάùl”®SßsÍ#_Cº§NÕ²˜$, '™úUM'™³må¼Cß,ì8¬XY]CñôÏ´[Y©2|â8Gˆu½||¡=øèãíwÿͶçÀF{òäsÀ–JŒ YwŒ9UÏy¤o 9rÀ•:\ɾo?19°b¸Æ5D o1[#ïéãGÛÓ'>ÓöLl‡omoÿ†»x&ïMío~ûw·cÇŸƒ™3íÄsÏc” rP‰ÀqàgŠl!Å$Q%Îe·ëS îpä¥úÈú€¼Jl‡I×u3%—ÿi%Œ> #p‘d„©‚ù(‰m—(=•b(O³„òìë ô›¼v½Câ­“q€aP¶vµxiwiÕ>†€á^/÷z£kip•Îü=ÜÜqAfùÖs T©ê¹ÛH¹I›åýhH:Iº¡Åå# @ŠU´¢ôÛ‹R‘Xs¼kðþx=rÕ0$À›:ÊP8.äÈ”¤g¸eé86î ͺâÑC˜.(vUºÙ(ß›9†Êy*Ïà@p†U¶g¿ÍLs“gûBúAX‡á–â pm?„ÿb/«q"WÆ¥<)d¯¥=¼µ¨Sçt_œ;&²RîÞqô$…|XÌ×sŒž=õ±ÆSpƒškí]˜Âã¿ ªµ¢-dÚ¦Ëßz‰æC£í«ÛÚBÆ©AÔÑÙÞt…4ÒD(0#³;{âÑ UE’ôf0 ‹CˆLèM.… +R!OÚ”ÿ‡2q³Ä1†å«<”:ÃMv¼ëÊÚæþYZZÍ€ ëܱ 3ÕKˆ7dw:"t ÒhdPY;Ê.e”q'ÊaŒâNè‡Ç®0)Î 'BC¡ K1ŒS`•#œm»\;àJÅ©œÂ}œWÔÀlS Lm+ò©E1’‹ÃuÕdàåvw&£™ô)o#«ù‰´)½ü—ŽNb2q4¤8©t)ÐèdAÈQá–À”Ö±±fÀxsÈ~là)L ¾>,ƒ,.9Ÿ'ßÅ냜lïK­ªL¶­ØB,Y £úw”š‡\«~Ú¡vpQq×H€Êµû+„ìô]•J# ÀËÊ*;a6–Ùt1(ÈÒ¹¹;«zƒ°3JB*Åc}Бþ,\J†1ÀÁ£Äm`(SñŒ´„ªtŸát#†+Žd8÷ùYùó|9;yØš…òõPgn9 }rŸ.G!+wéRÄds´½xl—Ä…SÝÜpÇLw!°êõ²2páQe'hÐ äuÌŒ6JqÖë3 ìâ¤GÚ̃œ%-4.ùЊ¬l¼ Õu€”|#—|QεƒÐŒAJ1*gQ[Û+íèæR;ÏbÚ¨4Òέ¬¶§>ÑV×­Â(Fá3Ó³Ù-³É`Ðî 0‰˜k£‹=²(°f<‡àñ†Þ Ðh’'€€žºÀÈÑ1út߾޹Ä}wïù»ðã~ag:˜óË«™û; 0” ‡Ð0…j¨*ývÍë|Ìsuœ'’V¦ÙóÈbÙ¨4ÒìW¼ùQ„ œž1&X[7ìâ-gžÏÒhêº.ÎcÕ“,*4oÊó ˜/ùé2¡È$½#x„¯ Êýz>‹à\_ïZåÚY€ëv nÔt·ãƒeV óÞàk\¥­¦QGè~|P°˜ø7Êy¢à»ç…_ðÉ Œri6÷ˆf]yTù]qn’­÷!ô—‰,´µMiÅ7€³ Áá™Jk®s¿Y‘@”A9…9Æ0ƾ´5ãù HÉ\Y«û fºÁrŠ7\OO³gá,`³É*¡u:sûw{X T8«ÌìÄá,ÀAnóöÚ*9&Š ôÎôrÊû8ã?!Ç\<¯¿’E2^f|@•Ý-kr%ÿixu Ÿd)sËÊ Ù£ 爇VÈ7D+ÃR°]‚ 1Ú€û•‘øÍÊÞIà/Î"Ä Õš\ªðPǹΧšgû©è¤"k^æk¤(¤êCK`ÕB3_Õ¥VÖýµnßó+QRì!–Pˆñ¼n%K Ä*—h i.謲9ó\ÞıÍ7>#°%nõîç6°ÓÁìú·† pΜ¥+`ÿþœ[°±³ók'=VyS!Á¦‚QðF"9C2Ob«Þnu»Û8Ýíä§+RžWœ!™º‡ [¯­é2\£B)DåjŒ¥4 9W§ÊÒ¨ãgÛÆð0zw‰^ú4®H[røguQFZ»£‹FAå$õ'_b®`½Qì‚‹€¢hÝ?Ȭc¿4ô©°-‹¥#Œ~ÑPeQgh'bÀ°á’îÆz®._`ÞÏÞ{ŸÆÉ½žÓ·ßGNlYÏÀ<¼·ÙM°%‡#y% Oàæí[l»&ˆs‹Å#£²¿ø¢x°¦û’p7ÐA~?/§QM™µØØ:*­”SÊ­ä@Äy싟{X+ ˆ •—§ˆog=ðºWXž™H=½Ti¤Õ“ã”ù¯ò8%+u#\2廸ç$)‘O§ 5U¥8x‡såÜS …‹ žû1òfÕŠü9…Üà57yfxžïÎW..û5Ú€P4YEp¨KdÆT’áÆ ÑEØäG¹ä]ÌŒM½N:ôS ËnC¸ÁçC·ÂÖs³ÇÇnüèÞ\ek¸#}o[#€k 3¬±HÈ¢IÑFahªÁ_y‡Jµ_’4 hay¥õK¡½Ë†å’¨œä^<ÖÑ’‰axGø*7áYÑüÂYÓ¹^/†X]Â*££eá¼øDxävÙ*këçcEÙÊ«1õ‹æ:»ü÷H(à,`6sP§2ƒ ÔàÀd¦# ké2hŠr¦Õ[?ŒDkCItà¤"LFW÷ì»XÊf ˜÷ö\`½[Ã}VïÂRM= ZÇ]ÁÜ8RE ±E˜Àó¨§K¯´¿4H„Bå£q¤Žç/à­èô±páØVö¬34,j`çÜ|×㬜¡¡pÛ6¼p„rå¼Öº RL35¬îœÔXh R¢³!ù_ÅVøåÓH°¡cˆî5e¹¥|1<íHk‰y„ó^[Ï’°ÿuAÈG¿\W…ý(bÉ9À_‚Êk*§ Jz¥@$M!*ti·ÌÇÉ-èü«(ùÉ8 c ®0c‘Ô9/ÞÍî%ñXêÄ™8ZS üÁ@¬_‘² GùÅQ .4À³mæ%è¤x´@:wñSå²i¤XÛæ!ó; ãÚó„Àqx‚*ª6]à]ÀÂÑ{ÊÓcñi¯àØg@#g”q>ïS5*C/÷¸ÂÚö4w}ÄûÿN U¼÷éø2'Å[=“ž8ŒÄ+;ôÉt‹Û‘3èwøëÆ!í–=—âŸlÊJNa»ñDNÀÚi+ íàPT”ýÂQr0åç°2Ɉ .ùñ#/GÏâ4rŸI¸Ò`]³Ì·÷#†*)½Ü×hpd‹E9ý J˜7‚D×ýa±[]Md™’DÛyæ1†T°Z‰w>]]‡¦duñ(»‚Qö2ï¼7å{Hz/Àª V|oëM¼®`1hã¯+Daúéø»À*"™?y K#•…˜ŸœáZƒ^ø'¿ 9‚¦®ŠíÞ jÿwG6´•gá/ùØÞ:ý˜”­4éåŽp4 ’Äv¡+ŒŸJU®ñø°¤»|Àšé Jw:¸ÊâÏä8ø³Ë0Zp:0lD‘ÖºÅZ¡I襪\ûû¢Iì -i ±+Ë<ù-a—Bé¢úÖIû0(-ÅpõÕàtÇ/–’øÁ¥l#7š kD©[8ŒA"ˆGz¤¼§:·~¡/×9¯KO½î £ `¨—9pÄ$0oJ@¥-©U„ÃVÍüU‰Q?J¦²uÈÀ †Ð¥õ‰å]XÒ*Áî@V¹zН±ž÷ü]ðõqޤ£ÆîbH»0 *Ý&áKlz|êhÐ ­m®Ã—žôq!>ï:F ]9€‘¾ê „,\¾9¥ÕriˆÌâ!FÈ`NËÄg½r¨ŽG@6V¹þ¥AüËr (y¦2—5€¶¨h®u”¿ðM6 Â\]ú5ÚÄ×KCØ h}@‰GÝU)\"•¨úì¨LzŠíõâÀ‰M‘—|„<Œòæ Gúà›c?‚ûÜ¢WÀÁ•KÀŽdO døà ;0ÉT¡žXyÕŸÕ,“4#BuKR©7âaWØ6OÑjX¾øzò”¢àÿ\TžxJ¹^+·l¬áb8äµL# þ‚-Í ~ò#7óD¶“Š6²SWax¡ìÔyáIWñ sûÕ[!í Ùÿ„!êBÏaY¯*'“ö^»cº5›_,fs¨ÆÄê iX®Úú8–»€U˜³_ò´ÄÊ ]/€òÜú¾Öc ÃA5 ÔK‹p¿³´<žÂ-¡ƒ*Q˸Düxõëë([~T~)¶”­—é¥Ðο‚Pàé6c&ݨ…6x¦uDl/Mˆ=OÈE©”M†ÙC™ã¯à'KºM1ÊͯäeÛiYp÷ïH Τ³Â¶gz¤Âµ]y×Ê©ø©V”%R}©„ºrz¿+­ÖOÆGpKe$Há ¹·n•©£Ã@u:<ÛéÁ­¸º¼…•x ½*Wü{Œ!HÍ/?‘Õ×\IÉHntÚQ k„]aM#p`(Q+£=Ô†‰ºÖJèT€`ÁãŽÐÈœ( #Ó0”í,@ÅK¼á]ؾÏBÖvGëns•Ч†ÜÚ(¹ÃR0Þ- ÓzÒëŠ èò‹' S:ýXÇ´³¯ß×Å I8åeeìvÙ¾I…ŠS8]Q"•Oo¦%¸ö\ÓðXã[ øØ#¡bêÇ2‚2Ló•s` x{è×lêQç_6‘®daÕR€&•¯`Ó¿ÉzqXUÉP½„Ui;‰¹8yU!Ú+%0ÇëìTñâYbÀ×ïøõ1€»…¢ B®?¸@ÔÜJÒP«¯-å{î K)ÌNA±Sa]:¥-†™zå­ÂS±6²¾°bÄÔ©Y„íë6l‡+ ay;‰œ$dÓ¦pH¤ ÁÇGãÎV.Úôz5®û 3ëÇO"jp”Ã({Ç'âwjµ")Q¶Ns}¹¯+@œÁš£óÁ3d"›A€&h£Šù±\ %¸¬X•õy Öº¶/(ÂÓ¼0ÌÀåó¾¶Ý!3<)ä”P£Ðpœ*Vt 0 èÝ“pœDÈ +]$ØAeS>€oê22/ Açg9*ä‡ô£0? ÊÀ±žp㜘enEÆêÅåP.]â‘FÑ\§Ó£åzø§Mo_r‹QR(.q«›Rþ€_ØWÞFÑ+¥+@Hë04x‰HM*¶÷o2£Õv$ܘ -“È> Ìší…“Oέ+`•…€Ig€†ðdb†1€ŒúÂHÿø½ Ìi#(h?âí²¾B)mÞ@ª®lð¬¾ÐϹBìÊ‚˜¤B2Ð vyÅ“õK‘À¶,3ù BôYƒu¹~d’mf‘‡FR‹uê¼ ÿàlò¡ð¬']ŸrRé‘¿0"¾ÒA/¯hg!Õ¯FŽ´ ŸÚÙôñ$’ÇY‰M(-AI_/wU‹•:>]ˆ!˜úZ¢©÷Á€"|­Ù_Ôª(Q€-™Yôñgä :Èu Ðߤ!ø^~_ £Á¬1=t%PaùÑû<êùy¸Et$VÊ.cÍ5Äȃ´jLxCÚœÐ:À,0…¼]¶êDå˧$TÄ·ÝY)MžËøEB¢b¯m‚‹ e—&Ú[n=ej—õí󻲩•pXOÃð—Ø"[ ;¼â P=_6‰Wd.$ Ê¿\ƒÌT°+D)fSªh›Üháakî ñtCR¡¼]C¯6¬:ôè˜ ïýgYxÅkL.9hš¦kh¬š^Ý\(胤„/’…Ž Åk¹Çj[†®àë–¯|•’¬ɨv%ŸàÊÒNþ5ÀoA.™)åSòÞ:T”¿u•SIKX7=i±©xÐjK×m´µ‚ÔŒJ#  70 HˆÖ«0$V$‚¤ Š°"F&9KêS/ì»M)·*ÀTlšFV‘ߣï,@C±+У4„©i|¾¶}¸à¬ÀQ¾Ñ¡VK¸Ò«áÙ>p¡:чlC³)¦,~è-£¥-4ô{ý 9F ŒN³íÒÈÿ@¿ãΰõ ~I ŒQH5#RK^NŸ5ÂN³ø*zt€…_‡‚fð™¬ùs-Ï5’‚I™V€òMY·°ÁÒh´XB,üu^ éMa6„”7)T‰/”uôÁ;:œÄ‹3¨c”–«ò2ˆ © ýò¡ ¥%!›“Dóàl‚WÜiÑòêöìœo.f|²Úüôó³ÜùF~"n‰é*¯l±^ÉEœ]FAƒ6ñˆWº"G.}Q¸­Ì—”ŽÕdÌ}é†ÒÖ²4N][J# @oÕ#LeédNä|PAÁH`Å2f GÒZãÚÙØH‘HC& ÌŸ¢Élƒ«þVÜé‹qùsqþÆŸ/ˆ2ˆíÂ9"{4WÖŒ×ÙßîýØCí¿ûL güZ^T‘ýÿJ ä#® Œêš"™-1$#–4É–ï/^]³ÿjçx>QqNE}kÙ44OA‹¿e°ÝN<»Ü¾øÉ'ÚÇ?ðh{àüä "üÉ_z'U~ jIç gƒÌ‚³#vdJý(’:޵2+Sš!¶„ü¨ZÃ…\«æC)×Ü]ÍUñÈÙåÒH0Ô¤ˆ­pgHVR¢W{T¨nÕª†u“g%Û);…©a˜×C¬°%6Æ`%~Â8¾nßùvüsüxÓaC¸„ñ[€üpä̰'PèÐÞ{þ>ð ‘oâñÏ=|º}âo_û-·2PtiEä;tÀ‰tûÞ¾Þ+9 R^òfpNT|uþþtûª \wkº¿U8Kˆg)‰Ÿ=¹Ö¾ðñgÛg?úLûÐû’Ä6ÃS½äöíÉNQv¢½ñ?¸Ž­¾ÞÅ›Cna6Ä9 ©ÈIùT”)¹vCž²¢fõŒ8åûŠ~yq•¥¬+9óð9K»ØQi¤ˆM¤*Ïé^æñäí„`Æãa †"zÌã?}µˆÐÉ—È^_b2"ß°6É/ŽKVû¯âgÞÏ «{®ïd?sýuúù9~‚ÅÕ=g*ÞB¼ o Ÿæ÷ n|Õ¾öóÿ݇ÚËî<Ü®»e;ÉoLM͵1~¨y‚ŸŸS€ÛLJÐéu¨¿ b—#>’6Zî \ŠöÌÔÖ*¶õü“Ú“?ßøôsü¾ñÃZZÒ5·,ð ¢.Z±lÍÂÞ0×~÷W¾Øî~Ëu¬dòŽ#žoç—Du_Ÿ›nGYÒZå—–wK ;k l;é ­”éxÛÈǃəòWù8HjËéåÓh áÆ6OÚxŸ¡tvÀC×,…{V£ÛPocâÜíÓ½­3Ø AsL@Í0ï áu1Çð ÛÑ>ŠJW°ˆÿî4å'ãQ–÷ œ 6ÙKÈú?3Ûþ÷Ÿú`ûŸx;°´¥3K,+ó䆦GÔÀJo«ðª7;• yp3Š¿ñgøu•ÑA³3'×Û1~ÏèÑŽ·ûïEñŸz6´øuäæ…¬E8¦ÉJF6ůˆj¬{çfÛcŸ?ÕúÔñv×ÛŽ°í΂!3@-‡/¼ ÒEI‡"æ«¢Au«FEäÝúæIwxH?$O#C_dL£ h“ºfˆq Q"Úf@Ã(—1(`‰rÞkh•h‡®¡’ï6&_%Â04Ë5Ü»üàá…»êHߟ]E¹:†³€,ùRêÔÐßv(KÇL Çã„Á½fÛÑçNµŸú¡_oßóC_Õn»ë0‘#V;ˆ5Z™ºQº®Ec*Ïåæeúê3'–Û±gηg=ÝyèùöùóÃCZ`ÆqÝmü¬÷Fä)º+Ç®Ð=‹îi€Í€ZÙwíTûý÷=ØnÿŠÃéÊ67ÝÐVŠò;ä’§ü*·ŠžvM“jôS©ºÐ«˜c‰†#I†9ÈÇNP§‘F€MJ黀3hGy¦X:G‰¬q€Š*‚ºp%ÁsS–Œíø³¾+q2á¹–>Îoœ¿°Ü®ºöêvíWÁ¡ÒÛÂ<À¯†ªìúÝ@¢}o]É`ПsÁÛ\ÎÊä>²·-^mÿý¿½æ-7%ü^wÓ¾¶pÀ'ŠˆiLXú{È­Ÿå÷O]Æpζçž<ÛžøâÙvòy^?ÞAéš[3­÷§sœþ" Êú$ËÓ¾‹ÇÇ×U€û“JNåoÿ¡yfÏÑ]<ß^yÏÕü õr Ljc¨ÖˆLÊ/B2=MÖðe„(Ù&z’k·ìX šçZ=ÔàQ}éõ|ò–Wœ×Ð0"4Ó+d€&|úûwu Vª°š!¨H4Až}¹Œh­…õt+ˆò/º%ŸöâÁ{&Æù1Æ¥óíÈSí5o¼µ½ï½kw¾öªšÐçºMÌ_èÝ2C#CÖ–ËȳIé‘á9 㥯ši÷ßÿXûô‡“Фù«|ëÈ^î n¢t~Ëõ2éª÷´n߇‚)D›zC”¬âO°ê¨r-´[ñgl'ˆ$ƒÑÎ1€m|Œ­¢ FpýTûÈoüq»ýµ×ÐmíA†,co^`ÔŽ<…K[DýJ¥.z¸8j‰3Τ’‘…2¯vÞùSÎGºˆ §éÃ2Î^œF€Þ™_Á¨€ÀñV.´Þ K”²ê °ŒãA¾Êi¾<7ϤÅʸ kZ¸Œ®n\hwÝó’ö¾ó1ê²ùƒ÷¸à´Ï{âTùÚÒÞ$ÂÀÐk×ô oÉ»?@¥—^};6q¸ðûør·xGn«ùÉ÷ µÐ¬M«ì6×ÒëžDúۿôò¨HžÁ£â¥Å©è´¿Zêkcå 8vYå ÝÏA~†ö¾=Ûù܉vëk÷1#X&º@T]’2—òsÀmÔ6ÊæuïJ(2V¹ é8|i [É3 H{å#×>:Áe’f<"©(E `ªÅy®@–¨Õ‰‹õR^a°!!±8²žÂQ€ýXá’—Nn´“§N¶[_Åoô½â†væÌ9|¾€¹>]?a?«° Q|Ê|“x•¥+€+…¯ÒƧ — p€6ÉšÍì/œ²ÛÀø­Èôßþ(“}ŠƒÈ|XXšà# û†á@O3Ý”w9šb%Ò² ŸF ßi¨Ò³‰•è0A™³Ê¿pT ?0Ö>üþπ˦råcTt@¬mÐC H£qÀé@Øè3M] oË>žœšê®Ò¢"w¢u=G<5>Áˆ®½v_û̇žn?p:}¹E ®•Mä¦Òà«<˜3p& §<±D=»S7„¸„ÑóbXÔQÒèÙ¨4Òl‡Ê¢|•à@)EJfYnB%„k†z¶ó¡‘04SÖ8j¹z¯ðzèµ¾FPïâYoÏ}¦½æ­7µ¯zǫډ3Ì ¹)µB¿o¿«<7T+`‡cGä–k$²k?\IoÅŒQ¸£õÞ©ríËÍ·ÔÊõV=Þ_GÕÃ8–×â“ÖU=X/†‘I^4<#U§EXŽ3”]" ÓM”Lïý¥ÅX©Ä0-WN;róBé"ÏrŒRlÉ*ÁÕeÚÑTWü/¿z Üè$h/û5ÒPdŽ$¹2!r.1¦ÐÚÁL¤çY¹¢B)YC¨6²¹ŒË !Õ°Üâí€ðìÙÓíÜêñö]ç«Ûɇ¨Ç Ô·„¹õË®§àù"H‹ïñ§i‹×´öãÿë÷¶?~ª­q£/ý1ôôÝB [® ê©*ÕP,J/…G2v@¡>Š­‘Äh˜ŒÊvé×È!õ…«‹®5¦}9Ædÿ.>œƒ$:0ByH?o€w*Íñ Û%~Rþs_äw8ÑÞt÷ëÚ?ùÙïo?òß SÁ#™5¹¡Ä¥Z•,Í&²ô<ÝF ¬üSæ–÷¾Ÿ+< å¨ìÌÒ ”¯…:b”dÁeR“—ѯ CºB¢ø8€Žhe™¨ªVÙ(!Í2àhH¹ŠsK™†.±:¥ aÛ¾×/\ÅãM<ñP{Å=¯iÿÙO¼»ýô?|O{ùƒ!¶ɮ`va"×Q 0U’ÉÝBþì¬ÞšnAø#T&™ì»gæ1 ¹<&pÔ`\~öþ‚I#¦G_E3Ϫ¤^ŸUIíVγ…·UŽ3ýâÃGÛÚ“,½bº}ç7¿½½é«_Ñ^zûÁ¶Â»}ö¾vôè1žrZ7ÅAû8ÜÙUªL²*2FP]E„òÒ±ÔO G¦TŠ€H‘u²èñ¤äÒ¯‘ ÊŒŽäUš‘ÃëÜ_ žKÎ.1«|½]¯wŠ©wåî#0½3)LËö:8&:ð«¥Û,b:ª=ò)„ÀBæ7}ó=íu÷¼œ™Ìõmñª©¶´zº=øôg™áœ z¬¦›#n h»×Ñ2QÊ#¤òå‰ *Û¼äÞi5М*~šºq*Cœ38ëwX\½84ÛT?nÈöÎ\)Ç|Ï»åõü!â|¼>©èÚ!²ú5!Jà„vjÄ à°Vôì>ð*¼dëÄñöÐØ}í­ïze»êÈßn?ùþv~j½½ê–#,±\ŒÀ]Ë·Oª}¾h5„9¢ƒÊÞ-«`;Ÿ.RÙâÛd—û 0qž‡NzYº @CÐxW¸§¯ÁŒOl´çž9ÖŽ=¢E<ý%Óí+ï¾§}Åß»½ÝöÊëÛÞC¼¼ŠG•Nž=Ö}èéŒi\Lsäûÿëuö¥T Dz~ård n“çV²¼®u>¼ˆTŽÈy‰´ê¦¾zK•+~4€´ P pÔ¾Bä0P‰7CD7«:BMD ¶Öêø/‡Y>a„| “IëÈ”×»Æ!S¶AX"Fp'Om÷qg펯xuûç¿øÃY$úåŸÿ=î·¶vÛÔÁ¬vyßÀ±„û õŽ”‘ËÇznî"2b7Ôk ÐAc¦”Ó5÷&o$—ÎuÏæèB4ÓÇùÝ!§ù}â6ÜxÛ;_Ù¾å›nl·Ýñ’vÃ-WµÅƒ(]?§Îžh_xüYÞdvšÈäϹðR x¨5ûjÇP%+e¬LÊ(2à<²å˜žŸb«¸žïíøDGºKi´­ípG¦jjHÔQ¶WJW0{Ž]«Ë#Èv@L5>®ÒUD–vÂUÝéãF -§¸ú5ÎM·ÔåÕ>ˆÑÈ8bPa:ë'N´Ï¬~¼ÝxÃ-í»à­í«ßñêö_ÿlû½~ ?Nð]}„»W‚žš6λ‚ÈTÏ-Ìß)ýËç ýeˆÝÁ,ã k癧϶1ˆÎû¬á3|Hw¼áêöê;ïh·¾ì†ö’[®n×Üp í;į–c8k›+íôÙçÛãã%Ög1ÀóÌÙõnyæã_¼¾<>Ò _zü˜TÒ gMUà·Kò:•rPΦ܇áÔö‚Ùq$N:®Ô¡Ü6»‘æ/øº‚T=QjµãÌcÝÌ pµÐD•.!µç½ˆ²\µ|tð¡ lÛ[??âÄ1‘C#¼@ðu˺ì©Ã ½}»uv«=øƒ¨ýϵ—Þpsû?ô¶ö®ï¼§=ôùgÚÃ>Ùþøá§ÚÇÿèa4r»¿K²»„sI>ÊÇ´Ï™œ%„ûLÛÊo»ýšöú»¯o×\sU;rÍ¡vÝKrci_[Ü?ßöpblJZVÚÙó§Û£Gmgðò~œb•_Ws›º‹cñt§c‰aų£Dø—?Îå=BÁLå¶§Ý KN#/«ä d¸À”ÕÈœ–u¹Gé”Ç`@¡ƒ9»Rm"à Cø^Jè¤ Ë•´Nú.ʬ§‚£L¼¸R)ÛüšŽ@\¬To(Ëõšâ|Œ]41Z¿úíþI[üöq\þôéSüžà>¶‚ÝÐ^óµ×¶7¼ýfFôx.¿#´Îö±cÏÉàð¯–?sr)ø:å›dßü>~ª†ûÞÿßw`1L¦ÙuT«ƒÐá­n¬0áNá…“íñ'Nµ¥ ü¢ Ï,¬Ó?d ‹²ö†R–G"^œ î]Z”à4 åçµenøÃ0v$ , ò­¾Ztü ñ¸OAEwõÀ‹WFa ñG}`ƒ»ŸWJ£ ú­ÐÀ“AêÞ/®¥öRn½x=G=½Bx‘ÈÁiˆ£vapÎQâ³+Xñ£OEÅáJ<¬ nlòÊ á†àbËž¹ù¶¸°—M¢ (sO»ùÚEÞ ^¿5œ1'ü‹ nck~¸¯€å¬1Æ8³¼ÔÖxíÌòò…ܬY#ßš×­À^æàƒr¤'¯h‘híô‹ƒKRÿÎû¼Ž“ì:‹ Óp™è'}Ô1U—hT *ìlÌÍu5Õ±Äç3–_ü)g+xz™¯Ñ@eiƒBNÓàÝe…T £ixRH¦#Ô¨~]B åeLj’ñC‚…Ø]Á™.`#‚F°‰ò01žÎaEÙÂy:íÌ„ár±ž“[·às³HŒ¸¼·Q8ô¬Rž†àºF<:yÑbŒYškô^Ï(¨8ÐQÛíp¥[Øs+–cn‘“[Š(^30&¯¸—¿Ÿx-nË5šJà¡ ^E×:eÒâ\ ¿¸hÏ 3KÆð9ˆ¹À]æ{´€XFHœ Œ6HØÚZƒ !x1V8ƺxÑ­Uk‚Ë× ÄZ+XseXjBGC³«8ɨÇҭ˵uT|ò€“¢Â§°2Šr¹0b'Ï,ÃzªÓ0ñ{?Ü­Z&ñVx4t~…(Únx–‘vðShUy O”©Ä ‡Âð·Z5Ì áÒ<(¢ÐÉ“¼)C{§]9©¿å ·ËO 2 ×è¼ ÕV2ùêŸê6h–qÙ‘Y9HÝ…LÃ}•«½(Ó¶0JWĶ%jôW»‰½,­#r‰W¸ý(B¯e ç ^#èý–BTP•/2E\p"2¼ÁR`0š(¢p„±Ð G•Sž%Q£ð—€l¢@¥Mz°íM9|hœ–gÏð! —<ù‰‚h”ææy2x£ž/Žt•…ÖŠ…ßÙîèÍ“ÖÐÂ×àLâ·œ³:æ;‹JnÚUûjçJ#mÓXŽ'êÑ«hã‹Ò.C&„HÉêúÊö¹´e4w80Wƒ ª›T3…Ó0Ò°+=Ó´9†6*KÅp͆z“-#,´ªRÄ•üQ %TT(Î׮ଫ@"<8ª3;<(3ô—·Y¥…Žj¯`Ã._e´Õ¦ðWγ]cÎmç+κâO@yŽE,Ïú=¸Ó™E§4ìº4„°…³"§çÀ!_X¾FOGïÜÔ¾µµmw¸®îBàjH/0l·s}f{™E­û×—±²q§B50±’Á"ÔmYšv­ÏsAT*OÊ5Y%ˆ¡ÄëÀ,¸zµ)u:Lká^«°\;Þ ~¯Ý!ã§’Þo›‚8=·}>”…î¡¶¥ó¢ÁR5i§½°üÚ=À·*Š;£ü¡±y¶·.àZŠžÊÓ°‹Q¯õReî°ã()ú…7Ðþ}Wf•_/ÒgêiÁ Oà›dzËn-Ÿn÷Sg¹ý7Ñmg7X/ÜWT^8¹õÑ¥ãÓwñº3ãÛ,¾l0ºÞf‹Ø|ð+m$­N"Í/"Š seå\`÷Ëô>Xfíö*­X WÀz¦L™‘Á'e‚É6Ž”•u0G¶ôq´~2Ó üz†uêÚ¶f¨ü‚2ä[eêM…¯ã¯Z*9‘N%>tK?)‚¾¸TjåU~®ƒŸü* @ùW©iÓqxQƒmùO¡^‘S”,Û—ì©<Ï5pgÊÏ»ŒbnZŸ~vû¶>hƒn r}_jïqŒÌªéÒ™\ýÌÁ‡ö^7wÛä$Ûئ?Æþ8‚©_\°a"ûëõB”Àˆ;a Õ8!Õ”i’üxjßßÇ >•Sžk©‚‘Ÿ°–6~•’#¢­fãâˆÒâÙœ;å+üÀ<²‚T¼Ý Šp€g¢ãç<±„B¤ð¥ÂÿÒkE‰ê·ÿÂË~ D|–E±f„$ù*Ü¡&xœ¥„¿¥Š¤EÇßÛs¬(Q‘VX ‹ÁWXw‹V @cÄ&&×éù'ÆÏ<3õЙ³Ï~ÄvmÐm·¯K ‚¶Ç¾gÁ®nOœ~zó÷_»ðcûo<ÆÍ˜½ yãÎS*’q’©ÐЇ:Wwñ%CA®A¶t„«25­±—T¢Ì”e;pá,O¡h(SˆÀÐ;…ͽ]"Gy—¢,/Q1–'ò× `e}J¥øxvB3ýµØFèÂTõÂ,ó„A øƒtâ׸‹nñ—Ò… ªxføž©º£ åÀ}´×ƒ·Ù¢fR‘ÂÏù@Ÿ°” ç<4+K’p„7»ÀÝË'®n§ŸÛüÅUt©NiW•R³¾ìï/º2¥êÝm|ï'Û͇ù¹›ß¼öÕSó§6—NíË$ÞõåŒâ‘ƒkéç"V5Ân-f˜Wž]Ó±è¹ÖsQȲâ³héFC³0¨§FКÁ CYïÐynØ¢ž¬ø·£LÊL Úˆ‘º MxÒAJˆ@®øŠaªñóç¿ÉëÂE%Îí†,ªA™y¹ OW¹õ½Î`üq$ñDñ ›*Li*Ç(Å:õ­BQ¦VxÉr5»Œ%`þà™Íõ¥|tú÷OœzþûϾ®=ª÷S¿{H—5ËÜøM7µésÓ³o=xhßÏÜøºõWL-œÝZ]šåf ®8ÄY¹£¢þÓÿ£lš¨$#%äRHñà¹a,d…kave1ÖÀ¸ÊPì=¨A_„Ó¦!š OÔ”LÊ $â‘fØÏ­¹„Ž åñ"*L€_CQ¨Ýˆ¤¹f%vY*Ó¤±ÐÒ7èM½>ËîÐ&Öç«*)ç(YÊ.Æ Æû"1Ô¡M"|ïøñÆñ”š˜ÞØš[Xn«çöŽ?õééûO?ñƒ‹kzì1ñøeÒ• @´·¿¥Í99ó¶=‹ûÿÑ5/ߺgñšó(€Û >ȃWã¼-*o†Ë\3H„GÒXø¢,â¤+!GÔUF×ìÍ·ßcŒÁ#:F-ÙN/XB…Û0|OÕÓ8"­(Caš,ÓüÆÇ|οÂváVશî|ÂOia7G—âS¼0%±¨$éÖõ¾«|€åMüÖ_Æ$(OÔy ˜wÈ»0í<Ú…WÝòÎgÙƒGåÇÇý‘DZå™rŽ=ùÂÏSHŸšf 8¹Ò­4îIDATïL}v¾=÷ÀøÇ.œ;÷ßn¼ðÁãÎ[”¹Ó,†KÓH°*Œ‹uâ–×·ù£'ÛÝ Ó¿÷ÐõSïÜ{ÝæÕóXŸññ+CSÔÁÛ¨„eør$0\«¢«–%Iº‚EÑ–¾Ä;x¾Ì+´ÞМ\êºMMÃÒãJ¸¥Â^áØ{C{*Öàpž;—HS˜c(Ç8sse~ï9¨pͨªøàªJ•þ2Ú¡%j ­˜ ÍÁ/½ÖS¶bÂÜT&繎,¤Ã.²Ž;Ê·~äUükÁÍÛU6ÖØszªyfâ艧×ÿŸ¥µ“¿põÁö™G>Ñ|øq¤òÅsEH%ð5mâð±Ã³mùصl yÃôä¯ZÜ7yçÜâøõìŠYÔà‹\‰«A‰ÀŒ~1”:\\­7+ÅXÒsªuA† ²ðbœ¨ü$6•ËAö97?×ÖÝ¡~À“æu.<7jHG™Ÿ”t<ÅWä[q庲z… â ²F^¼¸ùµpÐj y{cmìÜʹ­§/œÙ¸oeãÔ°ãíÚÜág>¶Ò>peå‹üK@*WcwÞÙ&—gÛÜù¥vÍ­G¸…~ZânÙ¬/ÈÐì2MNÕMÜÈ+%\¬‰*ºä»,мnWÖÅ º'Y¨/Z¿çõHÒ¯)™¬{I=ò:.Ëxt |x4ISY'U¾CïÀ§!õ:°ª~é·m^TçbYØ@¹vüVW®‹{&VX78ÁéŽónªççÚɹ•¶|ß}Ù E€èž „˧?‘ØÐMc_ó5mü±vÓäùcM.ΘX=j’èØË«Ö—¿ÿB%@°=3`ãÜÊ©ÍùÃ7mÜÔÛøÀbn åKàŸØ.ææ"c ¾^±.®þåó¿8 ôž!þ¤J¿˜œ?“\ ÀóÄ‹‹¿|ý$?‹Â_LÊ¿x1Ð/_ÿû# =þý!üË”þ»‘ÀÿýPMˆ(%IEND®B`‚amqp-2.2.2/docs/images/favicon.ico0000644000175000017500000000644413131450043016721 0ustar omeromer00000000000000‰PNG  IHDR szzôÿiCCPICC Profile8TÍOA-Ëbñ""6Æl1)4¼ƒ …FA£$z$lŒ ¿µí7`ãzûþþ¿¶Æ1d€ÀÄåœ!—ÏÔÉ:5¸5´wO›:âzÆi¦˜ âc l,2œµqÌâŒdú#>(¥â"ⶬÏ^ða;«5'ˆF¨" ¬iZÎ+*ñ¥û÷¶’ZqãÂÞ¤›Ñ Ž'±ËÓþQÃX‡Ð\qäâÄQÅLŽ8öq-›ºjó·§Êƒ‡ó_ym1ä)° ÉÔv Ú<«øVü,nˆÅgâ§?Ô¨ðK"¾øÌ³_ö9ØÝ ÷g±8 Œ¢Wi+¶Ú4˜E¾¾ç,¬ÕÜq®“KrÝ\Ü%î2×ËÅpvr|œñøÓ|ßÉ0ìî„?…¾üÆ÷œ€ìÛ«ÿ‰E|i‡¬Uó)kuÕòø“̘ìRõ•õYªЦÐ%Šç…>KDHjrG› ©ª`¹ ƒÐ*Éu{óì븕±Þ²@˪g3¯\ü‚ÿðšg›¬¼0Žœõla¼S‡,“+´êÜï@à5€‘?ÓeÏš¢øæ¼¯Õ¶ð~4<ع_«}R«í 1 LÂ'YfIDATX ½WKl\WþîcžöØãÔNÇm“ª„&TIP¨J „¡ªRP»B,Úª v,€R%V,*+VTB ‚„x…g- Ð¥UÄNb9~df)à¤@dÀÊ® b]S,$Cá À6Å?I·Ç×>°(‡†IJ€>2wa½‰,c¦“¼¸0hO!è$1U,v‡CƪÉ)×@@Ý…§àâG¦t…œX">—¬ÓËÕ`uµù¤âX´‘ŽoõãXM?Gpóžõ,£ÒyŠþp`ãÀæÝM4· Ô‚êõzZu.7Õc&°À–XI—Ó@žg$áɈ¤séžKj.J¼g mÊx§Ö¥<(ªÛýþ>˜®Jf[Ê,ãT©@Ã5½-ç²jÐÀƒáób‹SoöÓbå—\zI`ÉY̽ü"çURÒÐ1ÉËâ§!+^è‰ÔZfEŠ­ì¬¼$†W€†mÃQß‹´BR1iu§,áeãe[˜Öì•Òó8<½óFü]F½¬Þ¸Œ—c&íôŠk¯àó\}&»àd¬Tð¤DTá²îÁg²˜zÒK ¬¸jb2a'Éd¬~¼”È’4d6˘¾>|’YvÔ‡ÉG{º,'ö ÖôØ'Àþ¾G:®!¥•|İX–œ»fLòó’ñ ¡Ó¶÷€ž´¯ï“˜¨¢YQ†á‹W€u P»<4U)¯÷ÚKç2z¥÷" lçÝ0[ WÁ“Sû¾¶™c$ÊFáøP  Áz!o´F”ô^@RA̵hÙÊÂÔ24StX©ï²hUlpKFš¦~;¶A,Ä„1›ÖûÀ,P#ÉŸ½–Á”K©t? ›¸³Ñ§a-²å[«aX‹Q(&÷…tâêßîbîha‹¡ÒÂ#'xù¼`7?Í®…@¦YeȈÞóãèzEI¦¦š¸ÍMefv7Ö®áµ_uñðc‡9Qs¸èõ¶Ö¬^¹ƒëooá­K·ðÄ3gqæ£Óèõ7 D›TÔä7”Þ1#`‰!ïõe|u&T"*fVNÎãµ?ÿ33!ºÝ9üöâë¸ôÊ<¦; ìRln 8MtçZ˜áuêñ^þñXzðƒˆÚ’DÇ4I/%™ÚŽÍQ³¢Xiˆ¥¿K.¦¶ïnãØ©{äþzyr=6ö—ÙªÆl¥û8ºRgFX¿½…¸èâ³O<ŽZ›ÒxUÜKF óA{?‹W€Íc£hâs'ãdÆKôFo~ú>:ÜÁßÿr;ë;”´ RÏ ¦yhY\8ŽÅ¥.Ï`öHem€udܘPɯK0ö#9X<“Gì­ mie8˜ÕC@×òwpâKxàÜYdCJ™ûw7¬¸Á³d› 즛Xëíðà’2Œ5BœÙïšjRìœJ¡N(¶¹ŒÖuíÅb«<ÐÆ¡ýýÚÍ+¨7´[6·yzç;Ç£U>à.Çl—ÌÚ²Cžì£¨aÏ•‘P?#´N0ÛYLÝ^¾Ö˜ãa4ŽÚúS¦ÕJwû'Äã™–ý(ª#M*ÎqƒYì ¢;?ྩ›<Õ>!PÕnÕLµ$NzXÓ³F²k}èsýßOχ﷿`”ݧ|#«[OÍ>‹…ÌïªêøfÀcÚ DH‹5ÃàNuéÕïuΫ#Y’Ô®Û=û™êÙ¨^Þ_•Aé·zýuÓÆlæÅ†]}Ù¯ךô;n%š‚±W(§>¬ŠðÊ? ¾ììðýx¨ì úßW ü?r›HIEND®B`‚amqp-2.2.2/docs/index.rst0000644000175000017500000000054313131450043015166 0ustar omeromer00000000000000============================================= amqp - Python AMQP low-level client library ============================================= .. include:: includes/introduction.txt Contents ======== .. toctree:: :maxdepth: 2 reference/index changelog Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` amqp-2.2.2/docs/changelog.rst0000644000175000017500000000003213131450043015777 0ustar omeromer00000000000000.. include:: ../Changelog amqp-2.2.2/docs/make.bat0000644000175000017500000001646413131450043014743 0ustar omeromer00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. epub3 to make an epub3 echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled echo. coverage to run coverage check of the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) REM Check if sphinx-build is available and fallback to Python version if any %SPHINXBUILD% 1>NUL 2>NUL if errorlevel 9009 goto sphinx_python goto sphinx_ok :sphinx_python set SPHINXBUILD=python -m sphinx.__init__ %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) :sphinx_ok if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PROJ.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PROJ.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "epub3" ( %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "coverage" ( %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage if errorlevel 1 exit /b 1 echo. echo.Testing of coverage in the sources finished, look at the ^ results in %BUILDDIR%/coverage/python.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :end amqp-2.2.2/docs/includes/0000755000175000017500000000000013156466127015152 5ustar omeromer00000000000000amqp-2.2.2/docs/includes/introduction.txt0000644000175000017500000000720313156465105020431 0ustar omeromer00000000000000:Version: 2.2.2 :Web: https://amqp.readthedocs.io/ :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: https://kombu.readthedocs.io/ .. _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-2.2.2/docs/reference/0000755000175000017500000000000013156466127015302 5ustar omeromer00000000000000amqp-2.2.2/docs/reference/amqp.exceptions.rst0000644000175000017500000000040213131450043021125 0ustar omeromer00000000000000===================================================== ``amqp.exceptions`` ===================================================== .. contents:: :local: .. currentmodule:: amqp.exceptions .. automodule:: amqp.exceptions :members: :undoc-members: amqp-2.2.2/docs/reference/amqp.platform.rst0000644000175000017500000000037413131450043020600 0ustar omeromer00000000000000===================================================== ``amqp.platform`` ===================================================== .. contents:: :local: .. currentmodule:: amqp.platform .. automodule:: amqp.platform :members: :undoc-members: amqp-2.2.2/docs/reference/amqp.channel.rst0000644000175000017500000000037113131450043020361 0ustar omeromer00000000000000===================================================== ``amqp.channel`` ===================================================== .. contents:: :local: .. currentmodule:: amqp.channel .. automodule:: amqp.channel :members: :undoc-members: amqp-2.2.2/docs/reference/index.rst0000644000175000017500000000061113131450043017120 0ustar omeromer00000000000000.. _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.platform amqp.protocol amqp.sasl amqp.serialization amqp.spec amqp.utils amqp.five amqp-2.2.2/docs/reference/amqp.spec.rst0000644000175000017500000000036013131450043017701 0ustar omeromer00000000000000===================================================== ``amqp.spec`` ===================================================== .. contents:: :local: .. currentmodule:: amqp.spec .. automodule:: amqp.spec :members: :undoc-members: amqp-2.2.2/docs/reference/amqp.basic_message.rst0000644000175000017500000000041313131450043021533 0ustar omeromer00000000000000===================================================== ``amqp.basic_message`` ===================================================== .. contents:: :local: .. currentmodule:: amqp.basic_message .. automodule:: amqp.basic_message :members: :undoc-members: amqp-2.2.2/docs/reference/amqp.five.rst0000644000175000017500000000036013131450043017700 0ustar omeromer00000000000000===================================================== ``amqp.five`` ===================================================== .. contents:: :local: .. currentmodule:: amqp.five .. automodule:: amqp.five :members: :undoc-members: amqp-2.2.2/docs/reference/amqp.connection.rst0000644000175000017500000000040213131450043021103 0ustar omeromer00000000000000===================================================== ``amqp.connection`` ===================================================== .. contents:: :local: .. currentmodule:: amqp.connection .. automodule:: amqp.connection :members: :undoc-members: amqp-2.2.2/docs/reference/amqp.method_framing.rst0000644000175000017500000000041613131450043021734 0ustar omeromer00000000000000===================================================== ``amqp.method_framing`` ===================================================== .. contents:: :local: .. currentmodule:: amqp.method_framing .. automodule:: amqp.method_framing :members: :undoc-members: amqp-2.2.2/docs/reference/amqp.sasl.rst0000644000175000017500000000035413131450043017714 0ustar omeromer00000000000000===================================================== amqp.spec ===================================================== .. contents:: :local: .. currentmodule:: amqp.sasl .. automodule:: amqp.sasl :members: :undoc-members: amqp-2.2.2/docs/reference/amqp.serialization.rst0000644000175000017500000000041313131450043021623 0ustar omeromer00000000000000===================================================== ``amqp.serialization`` ===================================================== .. contents:: :local: .. currentmodule:: amqp.serialization .. automodule:: amqp.serialization :members: :undoc-members: amqp-2.2.2/docs/reference/amqp.transport.rst0000644000175000017500000000037713131450043021013 0ustar omeromer00000000000000===================================================== ``amqp.transport`` ===================================================== .. contents:: :local: .. currentmodule:: amqp.transport .. automodule:: amqp.transport :members: :undoc-members: amqp-2.2.2/docs/reference/amqp.protocol.rst0000644000175000017500000000037413131450043020615 0ustar omeromer00000000000000===================================================== ``amqp.protocol`` ===================================================== .. contents:: :local: .. currentmodule:: amqp.protocol .. automodule:: amqp.protocol :members: :undoc-members: amqp-2.2.2/docs/reference/amqp.utils.rst0000644000175000017500000000036313131450043020112 0ustar omeromer00000000000000===================================================== ``amqp.utils`` ===================================================== .. contents:: :local: .. currentmodule:: amqp.utils .. automodule:: amqp.utils :members: :undoc-members: amqp-2.2.2/docs/reference/amqp.abstract_channel.rst0000644000175000017500000000042413131450043022243 0ustar omeromer00000000000000===================================================== ``amqp.abstract_channel`` ===================================================== .. contents:: :local: .. currentmodule:: amqp.abstract_channel .. automodule:: amqp.abstract_channel :members: :undoc-members: amqp-2.2.2/docs/Makefile0000644000175000017500000001751113131450043014770 0ustar omeromer00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " epub3 to make an epub3" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" @echo " apicheck to verify that all modules are present in autodoc" .PHONY: clean clean: rm -rf $(BUILDDIR)/* .PHONY: html html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." .PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PROJ.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PROJ.qhc" .PHONY: applehelp applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." .PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/PROJ" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PROJ" @echo "# devhelp" .PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: epub3 epub3: $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 @echo @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." .PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." .PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: latexpdfja latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." .PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." .PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." .PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." .PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." .PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." .PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." .PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." .PHONY: coverage coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." .PHONY: apicheck apicheck: $(SPHINXBUILD) -b apicheck $(ALLSPHINXOPTS) $(BUILDDIR)/apicheck .PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." .PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." amqp-2.2.2/docs/_templates/0000755000175000017500000000000013156466127015501 5ustar omeromer00000000000000amqp-2.2.2/docs/_templates/sidebardonations.html0000644000175000017500000000601213131450043021675 0ustar omeromer00000000000000 amqp-2.2.2/docs/_static/0000755000175000017500000000000013156466127014772 5ustar omeromer00000000000000amqp-2.2.2/docs/_static/.keep0000644000175000017500000000000013131450043015664 0ustar omeromer00000000000000amqp-2.2.2/docs/templates/0000755000175000017500000000000013156466127015342 5ustar omeromer00000000000000amqp-2.2.2/docs/templates/readme.txt0000644000175000017500000000230413131450043017316 0ustar omeromer00000000000000===================================================================== Python AMQP 0.9.1 client library ===================================================================== |build-status| |coverage| |license| |wheel| |pyversion| |pyimp| .. include:: ../includes/introduction.txt .. |build-status| image:: https://secure.travis-ci.org/celery/py-amqp.png?branch=master :alt: Build status :target: https://travis-ci.org/celery/py-amqp .. |coverage| image:: https://codecov.io/github/celery/py-amqp/coverage.svg?branch=master :target: https://codecov.io/github/celery/py-amqp?branch=master .. |license| image:: https://img.shields.io/pypi/l/amqp.svg :alt: BSD License :target: https://opensource.org/licenses/BSD-3-Clause .. |wheel| image:: https://img.shields.io/pypi/wheel/amqp.svg :alt: Python AMQP can be installed via wheel :target: http://pypi.python.org/pypi/amqp/ .. |pyversion| image:: https://img.shields.io/pypi/pyversions/amqp.svg :alt: Supported Python versions. :target: http://pypi.python.org/pypi/amqp/ .. |pyimp| image:: https://img.shields.io/pypi/implementation/amqp.svg :alt: Support Python implementations. :target: http://pypi.python.org/pypi/amqp/ amqp-2.2.2/README.rst0000644000175000017500000001201313156465105014073 0ustar omeromer00000000000000===================================================================== Python AMQP 0.9.1 client library ===================================================================== |build-status| |coverage| |license| |wheel| |pyversion| |pyimp| :Version: 2.2.2 :Web: https://amqp.readthedocs.io/ :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: https://kombu.readthedocs.io/ .. _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)``. - Authentication Failure Notifications Instead of just closing the connection abruptly on invalid credentials, py-amqp will raise an ``AccessRefused`` error when connected to rabbitmq-server 3.2.0 or greater. - 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 .. |build-status| image:: https://secure.travis-ci.org/celery/py-amqp.png?branch=master :alt: Build status :target: https://travis-ci.org/celery/py-amqp .. |coverage| image:: https://codecov.io/github/celery/py-amqp/coverage.svg?branch=master :target: https://codecov.io/github/celery/py-amqp?branch=master .. |license| image:: https://img.shields.io/pypi/l/amqp.svg :alt: BSD License :target: https://opensource.org/licenses/BSD-3-Clause .. |wheel| image:: https://img.shields.io/pypi/wheel/amqp.svg :alt: Python AMQP can be installed via wheel :target: http://pypi.python.org/pypi/amqp/ .. |pyversion| image:: https://img.shields.io/pypi/pyversions/amqp.svg :alt: Supported Python versions. :target: http://pypi.python.org/pypi/amqp/ .. |pyimp| image:: https://img.shields.io/pypi/implementation/amqp.svg :alt: Support Python implementations. :target: http://pypi.python.org/pypi/amqp/ amqp-2.2.2/amqp.egg-info/0000755000175000017500000000000013156466127015044 5ustar omeromer00000000000000amqp-2.2.2/amqp.egg-info/not-zip-safe0000644000175000017500000000000113131451000017243 0ustar omeromer00000000000000 amqp-2.2.2/amqp.egg-info/dependency_links.txt0000644000175000017500000000000113156466127021112 0ustar omeromer00000000000000 amqp-2.2.2/amqp.egg-info/PKG-INFO0000644000175000017500000001557513156466127016156 0ustar omeromer00000000000000Metadata-Version: 1.1 Name: amqp Version: 2.2.2 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: BSD Description-Content-Type: UNKNOWN Description: ===================================================================== Python AMQP 0.9.1 client library ===================================================================== |build-status| |coverage| |license| |wheel| |pyversion| |pyimp| :Version: 2.2.2 :Web: https://amqp.readthedocs.io/ :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: https://kombu.readthedocs.io/ .. _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)``. - Authentication Failure Notifications Instead of just closing the connection abruptly on invalid credentials, py-amqp will raise an ``AccessRefused`` error when connected to rabbitmq-server 3.2.0 or greater. - 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 .. |build-status| image:: https://secure.travis-ci.org/celery/py-amqp.png?branch=master :alt: Build status :target: https://travis-ci.org/celery/py-amqp .. |coverage| image:: https://codecov.io/github/celery/py-amqp/coverage.svg?branch=master :target: https://codecov.io/github/celery/py-amqp?branch=master .. |license| image:: https://img.shields.io/pypi/l/amqp.svg :alt: BSD License :target: https://opensource.org/licenses/BSD-3-Clause .. |wheel| image:: https://img.shields.io/pypi/wheel/amqp.svg :alt: Python AMQP can be installed via wheel :target: http://pypi.python.org/pypi/amqp/ .. |pyversion| image:: https://img.shields.io/pypi/pyversions/amqp.svg :alt: Supported Python versions. :target: http://pypi.python.org/pypi/amqp/ .. |pyimp| image:: https://img.shields.io/pypi/implementation/amqp.svg :alt: Support Python implementations. :target: http://pypi.python.org/pypi/amqp/ Keywords: amqp rabbitmq cloudamqp messaging Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: License :: OSI Approved :: BSD License Classifier: Intended Audience :: Developers Classifier: Operating System :: OS Independent amqp-2.2.2/amqp.egg-info/top_level.txt0000644000175000017500000000000513156466127017571 0ustar omeromer00000000000000amqp amqp-2.2.2/amqp.egg-info/requires.txt0000644000175000017500000000001413156466127017437 0ustar omeromer00000000000000vine>=1.1.3 amqp-2.2.2/amqp.egg-info/SOURCES.txt0000644000175000017500000000325213156466127016732 0ustar omeromer00000000000000Changelog 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/platform.py amqp/protocol.py amqp/sasl.py amqp/serialization.py amqp/spec.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/requires.txt amqp.egg-info/top_level.txt docs/Makefile docs/changelog.rst docs/conf.py docs/index.rst docs/make.bat docs/_static/.keep docs/_templates/sidebardonations.html docs/images/celery_128.png docs/images/favicon.ico docs/includes/introduction.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.platform.rst docs/reference/amqp.protocol.rst docs/reference/amqp.sasl.rst docs/reference/amqp.serialization.rst docs/reference/amqp.spec.rst docs/reference/amqp.transport.rst docs/reference/amqp.utils.rst docs/reference/index.rst docs/templates/readme.txt extra/update_comments_from_spec.py requirements/default.txt requirements/docs.txt requirements/pkgutils.txt requirements/test-ci.txt requirements/test.txt t/__init__.py t/unit/__init__.py t/unit/test_abstract_channel.py t/unit/test_basic_message.py t/unit/test_channel.py t/unit/test_connection.py t/unit/test_exceptions.py t/unit/test_method_framing.py t/unit/test_platform.py t/unit/test_sasl.py t/unit/test_serialization.py t/unit/test_transport.py t/unit/test_utils.pyamqp-2.2.2/amqp/0000755000175000017500000000000013156466127013352 5ustar omeromer00000000000000amqp-2.2.2/amqp/connection.py0000644000175000017500000006005213154420412016047 0ustar omeromer00000000000000"""AMQP Connections.""" # Copyright (C) 2007-2008 Barry Pederson from __future__ import absolute_import, unicode_literals import logging import socket import uuid import warnings from vine import ensure_promise from . import __version__ from . import sasl from . import spec from .abstract_channel import AbstractChannel from .channel import Channel from .exceptions import ( AMQPDeprecationWarning, ChannelError, ResourceError, ConnectionForced, ConnectionError, error_for_code, RecoverableConnectionError, RecoverableChannelError, ) from .five import array, items, monotonic, range, values, string from .method_framing import frame_handler, frame_writer from .transport import Transport try: from ssl import SSLError except ImportError: # pragma: no cover class SSLError(Exception): # noqa pass W_FORCE_CONNECT = """\ The .{attr} attribute on the connection was accessed before the connection was established. This is supported for now, but will be deprecated in amqp 2.2.0. Since amqp 2.0 you have to explicitly call Connection.connect() before using the connection. """ START_DEBUG_FMT = """ Start from server, version: %d.%d, properties: %s, mechanisms: %s, locales: %s """.strip() __all__ = ['Connection'] AMQP_LOGGER = logging.getLogger('amqp') #: Default map for :attr:`Connection.library_properties` LIBRARY_PROPERTIES = { 'product': 'py-amqp', 'product_version': __version__, } #: Default map for :attr:`Connection.negotiate_capabilities` NEGOTIATE_CAPABILITIES = { 'consumer_cancel_notify': True, 'connection.blocked': True, 'authentication_failure_close': True, } class Connection(AbstractChannel): """AMQP Connection. 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 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) Authentication can be controlled by passing one or more `amqp.sasl.SASL` instances as the `authentication` parameter, or by using the userid and password parameters (for AMQPLAIN and PLAIN). 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. The "socket_settings" parameter is a dictionary defining tcp settings which will be applied as socket options. """ Channel = Channel #: Mapping of protocol extensions to enable. #: The server will report these in server_properties[capabilities], #: and if a key in this map is present the client will tell the #: server to either enable or disable the capability depending #: on the value set in this map. #: For example with: #: negotiate_capabilities = { #: 'consumer_cancel_notify': True, #: } #: The client will enable this capability if the server reports #: support for it, but if the value is False the client will #: disable the capability. negotiate_capabilities = NEGOTIATE_CAPABILITIES #: These are sent to the server to announce what features #: we support, type of client etc. library_properties = LIBRARY_PROPERTIES #: 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 successful writes to socket. bytes_sent = 0 #: Number of successful reads from socket. bytes_recv = 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 _METHODS = { spec.method(spec.Connection.Start, 'ooFSS'), spec.method(spec.Connection.OpenOk), spec.method(spec.Connection.Secure, 's'), spec.method(spec.Connection.Tune, 'BlB'), spec.method(spec.Connection.Close, 'BsBB'), spec.method(spec.Connection.Blocked), spec.method(spec.Connection.Unblocked), spec.method(spec.Connection.CloseOk), } _METHODS = {m.method_sig: m for m in _METHODS} connection_errors = ( ConnectionError, socket.error, IOError, OSError, ) channel_errors = (ChannelError,) recoverable_connection_errors = ( RecoverableConnectionError, socket.error, IOError, OSError, ) recoverable_channel_errors = ( RecoverableChannelError, ) def __init__(self, host='localhost:5672', userid='guest', password='guest', login_method=None, login_response=None, authentication=(), virtual_host='/', locale='en_US', client_properties=None, ssl=False, connect_timeout=None, channel_max=None, frame_max=None, heartbeat=0, on_open=None, on_blocked=None, on_unblocked=None, confirm_publish=False, on_tune_ok=None, read_timeout=None, write_timeout=None, socket_settings=None, frame_handler=frame_handler, frame_writer=frame_writer, **kwargs): self._connection_id = uuid.uuid4().hex channel_max = channel_max or 65535 frame_max = frame_max or 131072 if authentication: if isinstance(authentication, sasl.SASL): authentication = (authentication,) self.authentication = authentication elif login_method is not None and login_response is not None: self.authentication = (sasl.RAW(login_method, login_response),) elif userid is not None and password is not None: self.authentication = (sasl.GSSAPI(userid, fail_soft=True), sasl.AMQPLAIN(userid, password), sasl.PLAIN(userid, password)) else: raise ValueError("Must supply authentication or userid/password") self.client_properties = dict( self.library_properties, **client_properties or {} ) self.locale = locale self.host = host self.virtual_host = virtual_host self.on_tune_ok = ensure_promise(on_tune_ok) self.frame_handler_cls = frame_handler self.frame_writer_cls = frame_writer self._handshake_complete = False self.channels = {} # The connection object itself is treated as channel 0 super(Connection, self).__init__(self, 0) self._frame_writer = None self._on_inbound_frame = None 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 self.ssl = ssl self.read_timeout = read_timeout self.write_timeout = write_timeout self.socket_settings = socket_settings # Callbacks self.on_blocked = on_blocked self.on_unblocked = on_unblocked self.on_open = ensure_promise(on_open) 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 = [] self.connect_timeout = connect_timeout def __enter__(self): self.connect() return self def __exit__(self, *eargs): self.close() def then(self, on_success, on_error=None): return self.on_open.then(on_success, on_error) def _setup_listeners(self): self._callbacks.update({ spec.Connection.Start: self._on_start, spec.Connection.OpenOk: self._on_open_ok, spec.Connection.Secure: self._on_secure, spec.Connection.Tune: self._on_tune, spec.Connection.Close: self._on_close, spec.Connection.Blocked: self._on_blocked, spec.Connection.Unblocked: self._on_unblocked, spec.Connection.CloseOk: self._on_close_ok, }) def connect(self, callback=None): # Let the transport.py module setup the actual # socket connection to the broker. # if self.connected: return callback() if callback else None self.transport = self.Transport( self.host, self.connect_timeout, self.ssl, self.read_timeout, self.write_timeout, socket_settings=self.socket_settings, ) self.transport.connect() self.on_inbound_frame = self.frame_handler_cls( self, self.on_inbound_method) self.frame_writer = self.frame_writer_cls(self, self.transport) while not self._handshake_complete: self.drain_events(timeout=self.connect_timeout) def _warn_force_connect(self, attr): warnings.warn(AMQPDeprecationWarning( W_FORCE_CONNECT.format(attr=attr))) @property def transport(self): if self._transport is None: self._warn_force_connect('transport') self.connect() return self._transport @transport.setter def transport(self, transport): self._transport = transport @property def on_inbound_frame(self): if self._on_inbound_frame is None: self._warn_force_connect('on_inbound_frame') self.connect() return self._on_inbound_frame @on_inbound_frame.setter def on_inbound_frame(self, on_inbound_frame): self._on_inbound_frame = on_inbound_frame @property def frame_writer(self): if self._frame_writer is None: self._warn_force_connect('frame_writer') self.connect() return self._frame_writer @frame_writer.setter def frame_writer(self, frame_writer): self._frame_writer = frame_writer def _on_start(self, version_major, version_minor, server_properties, mechanisms, locales, argsig='FsSs'): client_properties = self.client_properties self.version_major = version_major self.version_minor = version_minor self.server_properties = server_properties if isinstance(mechanisms, string): mechanisms = mechanisms.encode('utf-8') self.mechanisms = mechanisms.split(b' ') self.locales = locales.split(' ') AMQP_LOGGER.debug( START_DEBUG_FMT, self.version_major, self.version_minor, self.server_properties, self.mechanisms, self.locales, ) # Negotiate protocol extensions (capabilities) scap = server_properties.get('capabilities') or {} cap = client_properties.setdefault('capabilities', {}) cap.update({ wanted_cap: enable_cap for wanted_cap, enable_cap in items(self.negotiate_capabilities) if scap.get(wanted_cap) }) if not cap: # no capabilities, server may not react well to having # this key present in client_properties, so we remove it. client_properties.pop('capabilities', None) for authentication in self.authentication: if authentication.mechanism in self.mechanisms: login_response = authentication.start(self) if login_response is not NotImplemented: break else: raise ConnectionError( "Couldn't find appropriate auth mechanism " "(can offer: {0}; available: {1})".format( b", ".join(m.mechanism for m in self.authentication if m.mechanism).decode(), b", ".join(self.mechanisms).decode())) self.send_method( spec.Connection.StartOk, argsig, (client_properties, authentication.mechanism, login_response, self.locale), ) def _on_secure(self, challenge): pass def _on_tune(self, channel_max, frame_max, server_heartbeat, argsig='BlB'): client_heartbeat = self.client_heartbeat or 0 self.channel_max = channel_max or self.channel_max self.frame_max = frame_max or self.frame_max self.server_heartbeat = server_heartbeat 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.send_method( spec.Connection.TuneOk, argsig, (self.channel_max, self.frame_max, self.heartbeat), callback=self._on_tune_sent, ) def _on_tune_sent(self, argsig='ssb'): self.send_method( spec.Connection.Open, argsig, (self.virtual_host, '', False), ) def _on_open_ok(self): self._handshake_complete = True self.on_open(self) def Transport(self, host, connect_timeout, ssl=False, read_timeout=None, write_timeout=None, socket_settings=None, **kwargs): return Transport( host, connect_timeout=connect_timeout, ssl=ssl, read_timeout=read_timeout, write_timeout=write_timeout, socket_settings=socket_settings, **kwargs) @property def connected(self): return self._transport and self._transport.connected def collect(self): try: if self._transport: self._transport.close() temp_list = [x for x in values(self.channels or {}) if x is not self] for ch in temp_list: ch.collect() 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), spec.Channel.Open) 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 channel(self, channel_id=None, callback=None): """Create new channel. Fetch a Channel object identified by the numeric channel_id, or create that object if it doesn't already exist. """ if self.channels is not None: try: return self.channels[channel_id] except KeyError: channel = self.Channel(self, channel_id, on_open=callback) channel.open() return channel raise RecoverableConnectionError('Connection already closed.') def is_alive(self): raise NotImplementedError('Use AMQP heartbeats') def drain_events(self, timeout=None): # read until message is ready while not self.blocking_read(timeout): pass def blocking_read(self, timeout=None): with self.transport.having_timeout(timeout): frame = self.transport.read_frame() return self.on_inbound_frame(frame) def on_inbound_method(self, channel_id, method_sig, payload, content): return self.channels[channel_id].dispatch_method( method_sig, payload, content, ) def close(self, reply_code=0, reply_text='', method_sig=(0, 0), argsig='BsBB'): """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 return self.send_method( spec.Connection.Close, argsig, (reply_code, reply_text, method_sig[0], method_sig[1]), wait=spec.Connection.CloseOk, ) def _on_close(self, reply_code, reply_text, class_id, method_id): """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. """ self._x_close_ok() raise error_for_code(reply_code, reply_text, (class_id, method_id), ConnectionError) 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(spec.Connection.CloseOk, callback=self._on_close_ok) def _on_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.collect() def _on_blocked(self): """Callback called when connection blocked. Notes: This is an RabbitMQ Extension. """ reason = 'connection blocked, see broker logs' if self.on_blocked: return self.on_blocked(reason) def _on_unblocked(self): if self.on_unblocked: return self.on_unblocked() def send_heartbeat(self): self.frame_writer(8, 0, None, None, None) def heartbeat_tick(self, rate=2): """Send heartbeat packets if necessary. Raises: ~amqp.exceptions.ConnectionForvced: if none have been received recently. Note: This should be called frequently, on the order of once per second. Keyword Arguments: rate (int): Previously used, but ignored now. """ AMQP_LOGGER.debug('heartbeat_tick : for connection %s', self._connection_id) if not self.heartbeat: return # treat actual data exchange in either direction as a heartbeat sent_now = self.bytes_sent recv_now = self.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() now = monotonic() AMQP_LOGGER.debug( 'heartbeat_tick : Prev sent/recv: %s/%s, ' 'now - %s/%s, monotonic - %s, ' 'last_heartbeat_sent - %s, heartbeat int. - %s ' 'for connection %s', self.prev_sent, self.prev_recv, sent_now, recv_now, now, self.last_heartbeat_sent, self.heartbeat, self._connection_id, ) self.prev_sent, self.prev_recv = sent_now, recv_now # send a heartbeat if it's time to do so if now > self.last_heartbeat_sent + self.heartbeat: AMQP_LOGGER.debug( 'heartbeat_tick: sending heartbeat for connection %s', self._connection_id) 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') @property def sock(self): return self.transport.sock @property def server_capabilities(self): return self.server_properties.get('capabilities') or {} amqp-2.2.2/amqp/exceptions.py0000644000175000017500000001577313154420412016103 0ustar omeromer00000000000000"""Exceptions used by amqp.""" # Copyright (C) 2007-2008 Barry Pederson from __future__ import absolute_import, unicode_literals from .five import python_2_unicode_compatible from .platform import pack, unpack __all__ = [ 'AMQPError', 'ConnectionError', 'ChannelError', 'RecoverableConnectionError', 'IrrecoverableConnectionError', 'RecoverableChannelError', 'IrrecoverableChannelError', 'ConsumerCancelled', 'ContentTooLarge', 'NoConsumers', 'ConnectionForced', 'InvalidPath', 'AccessRefused', 'NotFound', 'ResourceLocked', 'PreconditionFailed', 'FrameError', 'FrameSyntaxError', 'InvalidCommand', 'ChannelNotOpen', 'UnexpectedFrame', 'ResourceError', 'NotAllowed', 'AMQPNotImplementedError', 'InternalError', 'AMQPDeprecationWarning', ] class AMQPDeprecationWarning(UserWarning): """Warning for deprecated things.""" @python_2_unicode_compatible class AMQPError(Exception): """Base class for all AMQP exceptions.""" 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): """AMQP Connection Error.""" class ChannelError(AMQPError): """AMQP Channel Error.""" class RecoverableChannelError(ChannelError): """Exception class for recoverable channel errors.""" class IrrecoverableChannelError(ChannelError): """Exception class for irrecoverable channel errors.""" class RecoverableConnectionError(ConnectionError): """Exception class for recoverable connection errors.""" class IrrecoverableConnectionError(ConnectionError): """Exception class for irrecoverable connection errors.""" class Blocked(RecoverableConnectionError): """AMQP Connection Blocked Predicate.""" class ConsumerCancelled(RecoverableConnectionError): """AMQP Consumer Cancelled Predicate.""" class ContentTooLarge(RecoverableChannelError): """AMQP Content Too Large Error.""" code = 311 class NoConsumers(RecoverableChannelError): """AMQP No Consumers Error.""" code = 313 class ConnectionForced(RecoverableConnectionError): """AMQP Connection Forced Error.""" code = 320 class InvalidPath(IrrecoverableConnectionError): """AMQP Invalid Path Error.""" code = 402 class AccessRefused(IrrecoverableChannelError): """AMQP Access Refused Error.""" code = 403 class NotFound(IrrecoverableChannelError): """AMQP Not Found Error.""" code = 404 class ResourceLocked(RecoverableChannelError): """AMQP Resource Locked Error.""" code = 405 class PreconditionFailed(IrrecoverableChannelError): """AMQP Precondition Failed Error.""" code = 406 class FrameError(IrrecoverableConnectionError): """AMQP Frame Error.""" code = 501 class FrameSyntaxError(IrrecoverableConnectionError): """AMQP Frame Syntax Error.""" code = 502 class InvalidCommand(IrrecoverableConnectionError): """AMQP Invalid Command Error.""" code = 503 class ChannelNotOpen(IrrecoverableConnectionError): """AMQP Channel Not Open Error.""" code = 504 class UnexpectedFrame(IrrecoverableConnectionError): """AMQP Unexpected Frame.""" code = 505 class ResourceError(RecoverableConnectionError): """AMQP Resource Error.""" code = 506 class NotAllowed(IrrecoverableConnectionError): """AMQP Not Allowed Error.""" code = 530 class AMQPNotImplementedError(IrrecoverableConnectionError): """AMQP Not Implemented Error.""" code = 540 class InternalError(IrrecoverableConnectionError): """AMQP Internal Error.""" 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) 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-2.2.2/amqp/five.py0000644000175000017500000000026113131450043014633 0ustar omeromer00000000000000# -*- coding: utf-8 -*- """Python 2/3 compatibility.""" from __future__ import absolute_import, unicode_literals import sys import vine.five sys.modules[__name__] = vine.five amqp-2.2.2/amqp/__init__.py0000644000175000017500000000440313156465105015457 0ustar omeromer00000000000000"""Low-level AMQP client for Python (fork of amqplib).""" # Copyright (C) 2007-2008 Barry Pederson from __future__ import absolute_import, unicode_literals import re from collections import namedtuple __version__ = '2.2.2' __author__ = 'Barry Pederson' __maintainer__ = 'Ask Solem' __contact__ = 'pyamqp@celeryproject.org' __homepage__ = 'http://github.com/celery/py-amqp' __docformat__ = 'restructuredtext' # -eof meta- version_info_t = namedtuple('version_info_t', ( 'major', 'minor', 'micro', 'releaselevel', 'serial', )) # bumpversion can only search for {current_version} # so we have to parse the version here. _temp = re.match( r'(\d+)\.(\d+).(\d+)(.+)?', __version__).groups() VERSION = version_info = version_info_t( int(_temp[0]), int(_temp[1]), int(_temp[2]), _temp[3] or '', '') del(_temp) del(re) 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, ) from .utils import promise # noqa __all__ = [ 'Connection', 'Channel', 'Message', 'promise', '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', ] amqp-2.2.2/amqp/method_framing.py0000644000175000017500000001354113154420412016674 0ustar omeromer00000000000000"""Convert between frames and higher-level AMQP methods.""" # Copyright (C) 2007-2008 Barry Pederson from __future__ import absolute_import, unicode_literals from collections import defaultdict from . import spec from .basic_message import Message from .exceptions import UnexpectedFrame from .five import range from .platform import pack, unpack_from, pack_into from .utils import str_to_bytes __all__ = ['frame_handler', 'frame_writer'] #: Set of methods that require both a content frame and a body frame. _CONTENT_METHODS = frozenset([ spec.Basic.Return, spec.Basic.Deliver, spec.Basic.GetOk, ]) #: Number of bytes reserved for protocol in a content frame. #: We use this to calculate when a frame exceeeds the max frame size, #: and if it does not the message will fit into the preallocated buffer. FRAME_OVERHEAD = 40 def frame_handler(connection, callback, unpack_from=unpack_from, content_methods=_CONTENT_METHODS): """Create closure that reads frames.""" expected_types = defaultdict(lambda: 1) partial_messages = {} def on_frame(frame): frame_type, channel, buf = frame connection.bytes_recv += 1 if frame_type not in (expected_types[channel], 8): raise UnexpectedFrame( 'Received frame {0} while expecting type: {1}'.format( frame_type, expected_types[channel]), ) elif frame_type == 1: method_sig = unpack_from('>HH', buf, 0) if method_sig in content_methods: # Save what we've got so far and wait for the content-header partial_messages[channel] = Message( frame_method=method_sig, frame_args=buf, ) expected_types[channel] = 2 return False callback(channel, method_sig, buf, None) elif frame_type == 2: msg = partial_messages[channel] msg.inbound_header(buf) if not msg.ready: # wait for the content-body expected_types[channel] = 3 return False # bodyless message, we're done expected_types[channel] = 1 partial_messages.pop(channel, None) callback(channel, msg.frame_method, msg.frame_args, msg) elif frame_type == 3: msg = partial_messages[channel] msg.inbound_body(buf) if msg.ready: expected_types[channel] = 1 partial_messages.pop(channel, None) callback(channel, msg.frame_method, msg.frame_args, msg) elif frame_type == 8: # bytes_recv already updated pass return True return on_frame def frame_writer(connection, transport, pack=pack, pack_into=pack_into, range=range, len=len, bytes=bytes, str_to_bytes=str_to_bytes): """Create closure that writes frames.""" write = transport.write # memoryview first supported in Python 2.7 # Initial support was very shaky, so could be we have to # check for a bugfix release. buf = bytearray(connection.frame_max - 8) view = memoryview(buf) def write_frame(type_, channel, method_sig, args, content): chunk_size = connection.frame_max - 8 offset = 0 properties = None args = str_to_bytes(args) if content: properties = content._serialize_properties() body = content.body bodylen = len(body) framelen = ( len(args) + (len(properties) or 0) + bodylen + FRAME_OVERHEAD ) bigbody = framelen > chunk_size else: body, bodylen, bigbody = None, 0, 0 if bigbody: # ## SLOW: string copy and write for every frame frame = (b''.join([pack('>HH', *method_sig), args]) if type_ == 1 else b'') # encode method frame framelen = len(frame) write(pack('>BHI%dsB' % framelen, type_, channel, framelen, frame, 0xce)) if body: frame = b''.join([ pack('>HHQ', method_sig[0], 0, len(body)), properties, ]) framelen = len(frame) write(pack('>BHI%dsB' % framelen, 2, channel, framelen, frame, 0xce)) for i in range(0, bodylen, chunk_size): frame = body[i:i + chunk_size] framelen = len(frame) write(pack('>BHI%dsB' % framelen, 3, channel, framelen, str_to_bytes(frame), 0xce)) else: # ## FAST: pack into buffer and single write frame = (b''.join([pack('>HH', *method_sig), args]) if type_ == 1 else b'') framelen = len(frame) pack_into('>BHI%dsB' % framelen, buf, offset, type_, channel, framelen, frame, 0xce) offset += 8 + framelen if body is not None: frame = b''.join([ pack('>HHQ', method_sig[0], 0, len(body)), properties, ]) framelen = len(frame) pack_into('>BHI%dsB' % framelen, buf, offset, 2, channel, framelen, frame, 0xce) offset += 8 + framelen bodylen = len(body) if bodylen > 0: framelen = bodylen pack_into('>BHI%dsB' % framelen, buf, offset, 3, channel, framelen, str_to_bytes(body), 0xce) offset += 8 + framelen write(view[:offset]) connection.bytes_sent += 1 return write_frame amqp-2.2.2/amqp/serialization.py0000644000175000017500000004146713156462166016614 0ustar omeromer00000000000000"""Convert between bytestreams and higher-level AMQP types. 2007-11-05 Barry Pederson """ # Copyright (C) 2007 Barry Pederson from __future__ import absolute_import, unicode_literals import calendar import sys from datetime import datetime from decimal import Decimal from io import BytesIO from .spec import Basic from .exceptions import FrameSyntaxError from .five import int_types, long_t, string, string_t, items from .platform import pack, unpack_from from .utils import bytes_to_str as pstr_t, str_to_bytes ftype_t = chr if sys.version_info[0] == 3 else None ILLEGAL_TABLE_TYPE = """\ Table type {0!r} not handled by amqp. """ ILLEGAL_TABLE_TYPE_WITH_KEY = """\ Table type {0!r} for key {1!r} not handled by amqp. [value: {2!r}] """ ILLEGAL_TABLE_TYPE_WITH_VALUE = """\ Table type {0!r} not handled by amqp. [value: {1!r}] """ def _read_item(buf, offset=0, unpack_from=unpack_from, ftype_t=ftype_t): ftype = ftype_t(buf[offset]) if ftype_t else buf[offset] offset += 1 # 'S': long string if ftype == 'S': slen, = unpack_from('>I', buf, offset) offset += 4 val = pstr_t(buf[offset:offset + slen]) offset += slen # 's': short string elif ftype == 's': slen, = unpack_from('>B', buf, offset) offset += 1 val = pstr_t(buf[offset:offset + slen]) offset += slen # 'b': short-short int elif ftype == 'b': val, = unpack_from('>B', buf, offset) offset += 1 # 'B': short-short unsigned int elif ftype == 'B': val, = unpack_from('>b', buf, offset) offset += 1 # 'U': short int elif ftype == 'U': val, = unpack_from('>h', buf, offset) offset += 2 # 'u': short unsigned int elif ftype == 'u': val, = unpack_from('>H', buf, offset) offset += 2 # 'I': long int elif ftype == 'I': val, = unpack_from('>i', buf, offset) offset += 4 # 'i': long unsigned int elif ftype == 'i': val, = unpack_from('>I', buf, offset) offset += 4 # 'L': long long int elif ftype == 'L': val, = unpack_from('>q', buf, offset) offset += 8 # 'l': long long unsigned int elif ftype == 'l': val, = unpack_from('>Q', buf, offset) offset += 8 # 'f': float elif ftype == 'f': val, = unpack_from('>f', buf, offset) offset += 4 # 'd': double elif ftype == 'd': val, = unpack_from('>d', buf, offset) offset += 8 # 'D': decimal elif ftype == 'D': d, = unpack_from('>B', buf, offset) offset += 1 n, = unpack_from('>i', buf, offset) offset += 4 val = Decimal(n) / Decimal(10 ** d) # 'F': table elif ftype == 'F': tlen, = unpack_from('>I', buf, offset) offset += 4 limit = offset + tlen val = {} while offset < limit: keylen, = unpack_from('>B', buf, offset) offset += 1 key = pstr_t(buf[offset:offset + keylen]) offset += keylen val[key], offset = _read_item(buf, offset) # 'A': array elif ftype == 'A': alen, = unpack_from('>I', buf, offset) offset += 4 limit = offset + alen val = [] while offset < limit: v, offset = _read_item(buf, offset) val.append(v) # 't' (bool) elif ftype == 't': val, = unpack_from('>B', buf, offset) val = bool(val) offset += 1 # 'T': timestamp elif ftype == 'T': val, = unpack_from('>Q', buf, offset) offset += 8 val = datetime.utcfromtimestamp(val) # 'V': void elif ftype == 'V': val = None else: raise FrameSyntaxError( 'Unknown value in table: {0!r} ({1!r})'.format( ftype, type(ftype))) return val, offset def loads(format, buf, offset=0, ord=ord, unpack_from=unpack_from, _read_item=_read_item, pstr_t=pstr_t): """Deserialize amqp format. bit = b octet = o short = B long = l long long = L float = f shortstr = s longstr = S table = F array = A timestamp = T """ bitcount = bits = 0 values = [] append = values.append format = pstr_t(format) for p in format: if p == 'b': if not bitcount: bits = ord(buf[offset:offset + 1]) bitcount = 8 val = (bits & 1) == 1 bits >>= 1 bitcount -= 1 offset += 1 elif p == 'o': bitcount = bits = 0 val, = unpack_from('>B', buf, offset) offset += 1 elif p == 'B': bitcount = bits = 0 val, = unpack_from('>H', buf, offset) offset += 2 elif p == 'l': bitcount = bits = 0 val, = unpack_from('>I', buf, offset) offset += 4 elif p == 'L': bitcount = bits = 0 val, = unpack_from('>Q', buf, offset) offset += 8 elif p == 'f': bitcount = bits = 0 val, = unpack_from('>f', buf, offset) offset += 4 elif p == 's': bitcount = bits = 0 slen, = unpack_from('B', buf, offset) offset += 1 val = buf[offset:offset + slen].decode('utf-8') offset += slen elif p == 'S': bitcount = bits = 0 slen, = unpack_from('>I', buf, offset) offset += 4 val = buf[offset:offset + slen].decode('utf-8') offset += slen elif p == 'F': bitcount = bits = 0 tlen, = unpack_from('>I', buf, offset) offset += 4 limit = offset + tlen val = {} while offset < limit: keylen, = unpack_from('>B', buf, offset) offset += 1 key = pstr_t(buf[offset:offset + keylen]) offset += keylen val[key], offset = _read_item(buf, offset) elif p == 'A': bitcount = bits = 0 alen, = unpack_from('>I', buf, offset) offset += 4 limit = offset + alen val = [] while offset < limit: aval, offset = _read_item(buf, offset) val.append(aval) elif p == 'T': bitcount = bits = 0 val, = unpack_from('>Q', buf, offset) offset += 8 val = datetime.utcfromtimestamp(val) else: raise FrameSyntaxError(ILLEGAL_TABLE_TYPE.format(p)) append(val) return values, offset def _flushbits(bits, write, pack=pack): if bits: write(pack('B' * len(bits), *bits)) bits[:] = [] return 0 def dumps(format, values): """"Serialize AMQP arguments. Notes: bit = b octet = o short = B long = l long long = L shortstr = s longstr = S table = F array = A """ bitcount = 0 bits = [] out = BytesIO() write = out.write format = pstr_t(format) for i, val in enumerate(values): p = format[i] if p == 'b': val = 1 if val else 0 shift = bitcount % 8 if shift == 0: bits.append(0) bits[-1] |= (val << shift) bitcount += 1 elif p == 'o': bitcount = _flushbits(bits, write) write(pack('B', val)) elif p == 'B': bitcount = _flushbits(bits, write) write(pack('>H', int(val))) elif p == 'l': bitcount = _flushbits(bits, write) write(pack('>I', val)) elif p == 'L': bitcount = _flushbits(bits, write) write(pack('>Q', val)) elif p == 'f': bitcount = _flushbits(bits, write) write(pack('>f', val)) elif p == 's': val = val or '' bitcount = _flushbits(bits, write) if isinstance(val, string): val = val.encode('utf-8', 'surrogatepass') write(pack('B', len(val))) write(val) elif p == 'S': val = val or '' bitcount = _flushbits(bits, write) if isinstance(val, string): val = val.encode('utf-8', 'surrogatepass') write(pack('>I', len(val))) write(val) elif p == 'F': bitcount = _flushbits(bits, write) _write_table(val or {}, write, bits) elif p == 'A': bitcount = _flushbits(bits, write) _write_array(val or [], write, bits) elif p == 'T': write(pack('>Q', long_t(calendar.timegm(val.utctimetuple())))) _flushbits(bits, write) return out.getvalue() def _write_table(d, write, bits, pack=pack): out = BytesIO() twrite = out.write for k, v in items(d): if isinstance(k, string): k = k.encode('utf-8', 'surrogatepass') twrite(pack('B', len(k))) twrite(k) try: _write_item(v, twrite, bits) except ValueError: raise FrameSyntaxError( ILLEGAL_TABLE_TYPE_WITH_KEY.format(type(v), k, v)) table_data = out.getvalue() write(pack('>I', len(table_data))) write(table_data) def _write_array(l, write, bits, pack=pack): out = BytesIO() awrite = out.write for v in l: try: _write_item(v, awrite, bits) except ValueError: raise FrameSyntaxError( ILLEGAL_TABLE_TYPE_WITH_VALUE.format(type(v), v)) array_data = out.getvalue() write(pack('>I', len(array_data))) write(array_data) def _write_item(v, write, bits, pack=pack, string_t=string_t, bytes=bytes, string=string, bool=bool, float=float, int_types=int_types, Decimal=Decimal, datetime=datetime, dict=dict, list=list, tuple=tuple, None_t=None): if isinstance(v, (string_t, bytes)): if isinstance(v, string): v = v.encode('utf-8', 'surrogatepass') write(pack('>cI', b'S', len(v))) write(v) elif isinstance(v, bool): write(pack('>cB', b't', int(v))) elif isinstance(v, float): write(pack('>cd', b'd', v)) elif isinstance(v, int_types): if v > 2147483647 or v < -2147483647: write(pack('>cq', b'L', v)) else: write(pack('>ci', b'I', v)) elif isinstance(v, Decimal): sign, digits, exponent = v.as_tuple() v = 0 for d in digits: v = (v * 10) + d if sign: v = -v write(pack('>cBi', b'D', -exponent, v)) elif isinstance(v, datetime): write( pack('>cQ', b'T', long_t(calendar.timegm(v.utctimetuple())))) elif isinstance(v, dict): write(b'F') _write_table(v, write, bits) elif isinstance(v, (list, tuple)): write(b'A') _write_array(v, write, bits) elif v is None_t: write(b'V') else: raise ValueError() def decode_properties_basic(buf, offset=0, unpack_from=unpack_from, pstr_t=pstr_t): """Decode basic properties.""" properties = {} flags, = unpack_from('>H', buf, offset) offset += 2 if flags & 0x8000: slen, = unpack_from('>B', buf, offset) offset += 1 properties['content_type'] = pstr_t(buf[offset:offset + slen]) offset += slen if flags & 0x4000: slen, = unpack_from('>B', buf, offset) offset += 1 properties['content_encoding'] = pstr_t(buf[offset:offset + slen]) offset += slen if flags & 0x2000: _f, offset = loads('F', buf, offset) properties['application_headers'], = _f if flags & 0x1000: properties['delivery_mode'], = unpack_from('>B', buf, offset) offset += 1 if flags & 0x0800: properties['priority'], = unpack_from('>B', buf, offset) offset += 1 if flags & 0x0400: slen, = unpack_from('>B', buf, offset) offset += 1 properties['correlation_id'] = pstr_t(buf[offset:offset + slen]) offset += slen if flags & 0x0200: slen, = unpack_from('>B', buf, offset) offset += 1 properties['reply_to'] = pstr_t(buf[offset:offset + slen]) offset += slen if flags & 0x0100: slen, = unpack_from('>B', buf, offset) offset += 1 properties['expiration'] = pstr_t(buf[offset:offset + slen]) offset += slen if flags & 0x0080: slen, = unpack_from('>B', buf, offset) offset += 1 properties['message_id'] = pstr_t(buf[offset:offset + slen]) offset += slen if flags & 0x0040: properties['timestamp'], = unpack_from('>Q', buf, offset) offset += 8 if flags & 0x0020: slen, = unpack_from('>B', buf, offset) offset += 1 properties['type'] = pstr_t(buf[offset:offset + slen]) offset += slen if flags & 0x0010: slen, = unpack_from('>B', buf, offset) offset += 1 properties['user_id'] = pstr_t(buf[offset:offset + slen]) offset += slen if flags & 0x0008: slen, = unpack_from('>B', buf, offset) offset += 1 properties['app_id'] = pstr_t(buf[offset:offset + slen]) offset += slen if flags & 0x0004: slen, = unpack_from('>B', buf, offset) offset += 1 properties['cluster_id'] = pstr_t(buf[offset:offset + slen]) offset += slen return properties, offset PROPERTY_CLASSES = { Basic.CLASS_ID: decode_properties_basic, } class GenericContent(object): """Abstract base class for AMQP content. Subclasses should override the PROPERTIES attribute. """ CLASS_ID = None PROPERTIES = [('dummy', 's')] def __init__(self, frame_method=None, frame_args=None, **props): self.frame_method = frame_method self.frame_args = frame_args self.properties = props self._pending_chunks = [] self.body_received = 0 self.body_size = 0 self.ready = False 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] raise AttributeError(name) def _load_properties(self, class_id, buf, offset=0, classes=PROPERTY_CLASSES, unpack_from=unpack_from): """Load AMQP properties. 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'. """ # Read 16-bit shorts until we get one with a low bit set to zero props, offset = classes[class_id](buf, offset) self.properties = props return offset def _serialize_properties(self): """Serialize AMQP properties. 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 = [] sformat, svalues = [], [] props = self.properties props.setdefault('content_encoding', 'utf-8') for key, proptype in self.PROPERTIES: val = props.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': sformat.append(str_to_bytes(proptype)) svalues.append(val) shift -= 1 flags.append(flag_bits) result = BytesIO() write = result.write for flag_bits in flags: write(pack('>H', flag_bits)) write(dumps(b''.join(sformat), svalues)) return result.getvalue() def inbound_header(self, buf, offset=0): class_id, self.body_size = unpack_from('>HxxQ', buf, offset) offset += 12 self._load_properties(class_id, buf, offset) if not self.body_size: self.ready = True return offset def inbound_body(self, buf): chunks = self._pending_chunks self.body_received += len(buf) if self.body_received >= self.body_size: if chunks: chunks.append(buf) self.body = bytes().join(chunks) chunks[:] = [] else: self.body = buf self.ready = True else: chunks.append(buf) amqp-2.2.2/amqp/protocol.py0000644000175000017500000000053513131450043015547 0ustar omeromer00000000000000"""Protocol data.""" from __future__ import absolute_import, unicode_literals 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-2.2.2/amqp/spec.py0000644000175000017500000000420213131450043014633 0ustar omeromer00000000000000"""AMQP Spec.""" from __future__ import absolute_import, unicode_literals from collections import namedtuple method_t = namedtuple('method_t', ('method_sig', 'args', 'content')) def method(method_sig, args=None, content=False): """Create amqp method specification tuple.""" return method_t(method_sig, args, content) class Connection: """AMQ Connection class.""" CLASS_ID = 10 Start = (10, 10) StartOk = (10, 11) Secure = (10, 20) SecureOk = (10, 21) Tune = (10, 30) TuneOk = (10, 31) Open = (10, 40) OpenOk = (10, 41) Close = (10, 50) CloseOk = (10, 51) Blocked = (10, 60) Unblocked = (10, 61) class Channel: """AMQ Channel class.""" CLASS_ID = 20 Open = (20, 10) OpenOk = (20, 11) Flow = (20, 20) FlowOk = (20, 21) Close = (20, 40) CloseOk = (20, 41) class Exchange: """AMQ Exchange class.""" CLASS_ID = 40 Declare = (40, 10) DeclareOk = (40, 11) Delete = (40, 20) DeleteOk = (40, 21) Bind = (40, 30) BindOk = (40, 31) Unbind = (40, 40) UnbindOk = (40, 51) class Queue: """AMQ Queue class.""" CLASS_ID = 50 Declare = (50, 10) DeclareOk = (50, 11) Bind = (50, 20) BindOk = (50, 21) Purge = (50, 30) PurgeOk = (50, 31) Delete = (50, 40) DeleteOk = (50, 41) Unbind = (50, 50) UnbindOk = (50, 51) class Basic: """AMQ Basic class.""" CLASS_ID = 60 Qos = (60, 10) QosOk = (60, 11) Consume = (60, 20) ConsumeOk = (60, 21) Cancel = (60, 30) CancelOk = (60, 31) Publish = (60, 40) Return = (60, 50) Deliver = (60, 60) Get = (60, 70) GetOk = (60, 71) GetEmpty = (60, 72) Ack = (60, 80) Nack = (60, 120) Reject = (60, 90) RecoverAsync = (60, 100) Recover = (60, 110) RecoverOk = (60, 111) class Confirm: """AMQ Confirm class.""" CLASS_ID = 85 Select = (85, 10) SelectOk = (85, 11) class Tx: """AMQ Tx class.""" CLASS_ID = 90 Select = (90, 10) SelectOk = (90, 11) Commit = (90, 20) CommitOk = (90, 21) Rollback = (90, 30) RollbackOk = (90, 31) amqp-2.2.2/amqp/platform.py0000644000175000017500000000364513131450043015537 0ustar omeromer00000000000000"""Platform compatibility.""" from __future__ import absolute_import, unicode_literals import struct import sys import platform import re # 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 RE_NUM = re.compile(r'(\d+).+') def _linux_version_to_tuple(s): # type: (str) -> Tuple[int, int, int] return tuple(map(_versionatom, s.split('.')[:3])) def _versionatom(s): # type: (str) -> int if s.isdigit(): return int(s) match = RE_NUM.match(s) return int(match.groups()[0]) if match else 0 LINUX_VERSION = None if sys.platform.startswith('linux'): LINUX_VERSION = _linux_version_to_tuple(platform.release()) try: from socket import TCP_USER_TIMEOUT HAS_TCP_USER_TIMEOUT = True except ImportError: # pragma: no cover # should be in Python 3.6+ on Linux. TCP_USER_TIMEOUT = 18 HAS_TCP_USER_TIMEOUT = LINUX_VERSION and LINUX_VERSION >= (2, 6, 37) HAS_TCP_MAXSEG = True # According to MSDN Windows platforms support getsockopt(TCP_MAXSSEG) but not # setsockopt(TCP_MAXSEG) on IPPROTO_TCP sockets. if sys.platform.startswith('win'): HAS_TCP_MAXSEG = False if sys.version_info < (2, 7, 7): import functools def _to_bytes_arg(fun): @functools.wraps(fun) def _inner(s, *args, **kwargs): return fun(s.encode(), *args, **kwargs) return _inner pack = _to_bytes_arg(struct.pack) pack_into = _to_bytes_arg(struct.pack_into) unpack = _to_bytes_arg(struct.unpack) unpack_from = _to_bytes_arg(struct.unpack_from) else: pack = struct.pack pack_into = struct.pack_into unpack = struct.unpack unpack_from = struct.unpack_from __all__ = [ 'LINUX_VERSION', 'SOL_TCP', 'TCP_USER_TIMEOUT', 'HAS_TCP_USER_TIMEOUT', 'HAS_TCP_MAXSEG', 'pack', 'pack_into', 'unpack', 'unpack_from', ] amqp-2.2.2/amqp/utils.py0000644000175000017500000000507213131450043015047 0ustar omeromer00000000000000"""Compatibility utilities.""" from __future__ import absolute_import, unicode_literals import logging import sys # enables celery 3.1.23 to start again from vine import promise # noqa from vine.utils import wraps from .five import string_t is_py3k = sys.version_info[0] == 3 try: import fcntl except ImportError: # pragma: no cover fcntl = None # noqa try: from os import set_cloexec # Python 3.4? except ImportError: # pragma: no cover def set_cloexec(fd, cloexec): # noqa """Set flag to close fd after exec.""" if fcntl is None: return 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): """Get exception errno (if set). Notes: :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 def coro(gen): """Decorator to mark generator as a co-routine.""" @wraps(gen) def _boot(*args, **kwargs): co = gen(*args, **kwargs) next(co) return co return _boot if is_py3k: # pragma: no cover def str_to_bytes(s): """Convert str to bytes.""" if isinstance(s, str): return s.encode() return s def bytes_to_str(s): """Convert bytes to str.""" if isinstance(s, bytes): return s.decode() return s else: def str_to_bytes(s): # noqa """Convert str to bytes.""" if isinstance(s, unicode): return s.encode() return s def bytes_to_str(s): # noqa """Convert bytes to str.""" return s class NullHandler(logging.Handler): """A logging handler that does nothing.""" def emit(self, record): pass def get_logger(logger): """Get logger by name.""" if isinstance(logger, string_t): logger = logging.getLogger(logger) if not logger.handlers: logger.addHandler(NullHandler()) return logger amqp-2.2.2/amqp/abstract_channel.py0000644000175000017500000001010013154420412017170 0ustar omeromer00000000000000"""Code common to Connection and Channel objects.""" # Copyright (C) 2007-2008 Barry Pederson ) from __future__ import absolute_import, unicode_literals from vine import ensure_promise, promise from .exceptions import AMQPNotImplementedError, RecoverableConnectionError from .five import bytes_if_py2 from .serialization import dumps, loads __all__ = ['AbstractChannel'] class AbstractChannel(object): """Superclass for Connection and Channel. The connection is treated as channel 0, then comes 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 self._pending = {} self._callbacks = {} self._setup_listeners() def __enter__(self): return self def __exit__(self, *exc_info): self.close() def send_method(self, sig, format=None, args=None, content=None, wait=None, callback=None, returns_tuple=False): p = promise() conn = self.connection if conn is None: raise RecoverableConnectionError('connection already closed') args = dumps(format, args) if format else bytes_if_py2('') try: conn.frame_writer(1, self.channel_id, sig, args, content) except StopIteration: raise RecoverableConnectionError('connection already closed') # TODO temp: callback should be after write_method ... ;) if callback: p.then(callback) p() if wait: return self.wait(wait, returns_tuple=returns_tuple) return p def close(self): """Close this Channel or Connection.""" raise NotImplementedError('Must be overriden in subclass') def wait(self, method, callback=None, timeout=None, returns_tuple=False): p = ensure_promise(callback) pending = self._pending prev_p = [] if not isinstance(method, list): method = [method] for m in method: prev_p.append(pending.get(m)) pending[m] = p try: while not p.ready: self.connection.drain_events(timeout=timeout) if p.value: args, kwargs = p.value return args if returns_tuple else (args and args[0]) finally: for i, m in enumerate(method): if prev_p[i] is not None: pending[m] = prev_p[i] else: pending.pop(m, None) def dispatch_method(self, method_sig, payload, 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._METHODS[method_sig] except KeyError: raise AMQPNotImplementedError( 'Unknown AMQP method {0!r}'.format(method_sig)) try: listeners = [self._callbacks[method_sig]] except KeyError: listeners = None try: one_shot = self._pending.pop(method_sig) except KeyError: if not listeners: return else: if listeners is None: listeners = [one_shot] else: listeners.append(one_shot) args = [] if amqp_method.args: args, _ = loads(amqp_method.args, payload, 4) if amqp_method.content: args.append(content) for listener in listeners: listener(*args) #: Placeholder, the concrete implementations will have to #: supply their own versions of _METHOD_MAP _METHODS = {} amqp-2.2.2/amqp/channel.py0000644000175000017500000021306613154420412015325 0ustar omeromer00000000000000"""AMQP Channels.""" # Copyright (C) 2007-2008 Barry Pederson from __future__ import absolute_import, unicode_literals import logging import socket from collections import defaultdict from warnings import warn from vine import ensure_promise from . import spec from .abstract_channel import AbstractChannel from .exceptions import ( ChannelError, ConsumerCancelled, RecoverableChannelError, RecoverableConnectionError, error_for_code, ) from .five import Queue from .protocol import queue_declare_ok_t __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.\ """ REJECTED_MESSAGE_WITHOUT_CALLBACK = """\ Rejecting message with delivery tag %r for reason of having no callbacks. consumer_tag=%r exchange=%r routing_key=%r.\ """ class VDeprecationWarning(DeprecationWarning): pass class Channel(AbstractChannel): """AMQP Channel. 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 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. """ _METHODS = { spec.method(spec.Channel.Close, 'BsBB'), spec.method(spec.Channel.CloseOk), spec.method(spec.Channel.Flow, 'b'), spec.method(spec.Channel.FlowOk, 'b'), spec.method(spec.Channel.OpenOk), spec.method(spec.Exchange.DeclareOk), spec.method(spec.Exchange.DeleteOk), spec.method(spec.Exchange.BindOk), spec.method(spec.Exchange.UnbindOk), spec.method(spec.Queue.BindOk), spec.method(spec.Queue.UnbindOk), spec.method(spec.Queue.DeclareOk, 'sll'), spec.method(spec.Queue.DeleteOk, 'l'), spec.method(spec.Queue.PurgeOk, 'l'), spec.method(spec.Basic.Cancel, 's'), spec.method(spec.Basic.CancelOk, 's'), spec.method(spec.Basic.ConsumeOk, 's'), spec.method(spec.Basic.Deliver, 'sLbss', content=True), spec.method(spec.Basic.GetEmpty, 's'), spec.method(spec.Basic.GetOk, 'Lbssl', content=True), spec.method(spec.Basic.QosOk), spec.method(spec.Basic.RecoverOk), spec.method(spec.Basic.Return, 'Bsss', content=True), spec.method(spec.Tx.CommitOk), spec.method(spec.Tx.RollbackOk), spec.method(spec.Tx.SelectOk), spec.method(spec.Confirm.SelectOk), spec.method(spec.Basic.Ack, 'Lb'), } _METHODS = {m.method_sig: m for m in _METHODS} def __init__(self, connection, channel_id=None, auto_decode=True, on_open=None): if channel_id: connection._claim_channel_id(channel_id) else: channel_id = connection._get_free_channel_id() AMQP_LOGGER.debug('using channel_id: %s', 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() self.on_open = ensure_promise(on_open) # 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 def then(self, on_success, on_error=None): return self.on_open.then(on_success, on_error) def _setup_listeners(self): self._callbacks.update({ spec.Channel.Close: self._on_close, spec.Channel.CloseOk: self._on_close_ok, spec.Channel.Flow: self._on_flow, spec.Channel.OpenOk: self._on_open_ok, spec.Basic.Cancel: self._on_basic_cancel, spec.Basic.CancelOk: self._on_basic_cancel_ok, spec.Basic.Deliver: self._on_basic_deliver, spec.Basic.Return: self._on_basic_return, spec.Basic.Ack: self._on_basic_ack, }) def collect(self): """Tear down this object. Best called after we've agreed to close with the server. """ AMQP_LOGGER.debug('Closed channel #%s', 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.open() def close(self, reply_code=0, reply_text='', method_sig=(0, 0), argsig='BsBB'): """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: is_closed = ( not self.is_open or self.connection is None or self.connection.channels is None ) if is_closed: return return self.send_method( spec.Channel.Close, argsig, (reply_code, reply_text, method_sig[0], method_sig[1]), wait=spec.Channel.CloseOk, ) finally: self.connection = None def _on_close(self, reply_code, reply_text, class_id, method_id): """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. """ self.send_method(spec.Channel.CloseOk) self._do_revive() raise error_for_code( reply_code, reply_text, (class_id, method_id), ChannelError, ) def _on_close_ok(self): """Confirm a channel close. This method confirms a Channel.Close method and tells the recipient that it is safe to release resources for the channel and close the socket. RULE: A peer that detects a socket closure without having received a Channel.Close-Ok handshake method SHOULD log the error. """ self.collect() 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. """ return self.send_method( spec.Channel.Flow, 'b', (active,), wait=spec.Channel.FlowOk, ) def _on_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. """ self.active = active 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. """ return self.send_method(spec.Channel.FlowOk, 'b', (active,)) def 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 return self.send_method( spec.Channel.Open, 's', ('',), wait=spec.Channel.OpenOk, ) def _on_open_ok(self): """Signal that the channel is ready. This method signals to the client that the channel is ready for use. """ self.is_open = True self.on_open(self) 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, argsig='BssbbbbbF'): """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. """ if auto_delete: warn(VDeprecationWarning(EXCHANGE_AUTODELETE_DEPRECATED)) self.send_method( spec.Exchange.Declare, argsig, (0, exchange, type, passive, durable, auto_delete, False, nowait, arguments), wait=None if nowait else spec.Exchange.DeclareOk, ) def exchange_delete(self, exchange, if_unused=False, nowait=False, argsig='Bsbb'): """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. """ return self.send_method( spec.Exchange.Delete, argsig, (0, exchange, if_unused, nowait), wait=None if nowait else spec.Exchange.DeleteOk, ) def exchange_bind(self, destination, source='', routing_key='', nowait=False, arguments=None, argsig='BsssbF'): """Bind 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. """ return self.send_method( spec.Exchange.Bind, argsig, (0, destination, source, routing_key, nowait, arguments), wait=None if nowait else spec.Exchange.BindOk, ) def exchange_unbind(self, destination, source='', routing_key='', nowait=False, arguments=None, argsig='BsssbF'): """Unbind 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. """ return self.send_method( spec.Exchange.Unbind, argsig, (0, destination, source, routing_key, nowait, arguments), wait=None if nowait else spec.Exchange.UnbindOk, ) ############# # # 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, argsig='BsssbF'): """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. """ return self.send_method( spec.Queue.Bind, argsig, (0, queue, exchange, routing_key, nowait, arguments), wait=None if nowait else spec.Queue.BindOk, ) def queue_unbind(self, queue, exchange, routing_key='', nowait=False, arguments=None, argsig='BsssF'): """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. """ return self.send_method( spec.Queue.Unbind, argsig, (0, queue, exchange, routing_key, arguments), wait=None if nowait else spec.Queue.UnbindOk, ) def queue_declare(self, queue='', passive=False, durable=False, exclusive=False, auto_delete=True, nowait=False, arguments=None, argsig='BsbbbbbF'): """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 """ self.send_method( spec.Queue.Declare, argsig, (0, queue, passive, durable, exclusive, auto_delete, nowait, arguments), ) if not nowait: return queue_declare_ok_t(*self.wait( spec.Queue.DeclareOk, returns_tuple=True, )) def queue_delete(self, queue='', if_unused=False, if_empty=False, nowait=False, argsig='Bsbbb'): """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. """ return self.send_method( spec.Queue.Delete, argsig, (0, queue, if_unused, if_empty, nowait), wait=None if nowait else spec.Queue.DeleteOk, ) def queue_purge(self, queue='', nowait=False, argsig='Bsb'): """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 """ return self.send_method( spec.Queue.Purge, argsig, (0, queue, nowait), wait=None if nowait else spec.Queue.PurgeOk, ) ############# # # 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 # acknowledgments on Basic content. # def basic_ack(self, delivery_tag, multiple=False, argsig='Lb'): """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. """ return self.send_method( spec.Basic.Ack, argsig, (delivery_tag, multiple), ) def basic_cancel(self, consumer_tag, nowait=False, argsig='sb'): """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) return self.send_method( spec.Basic.Cancel, argsig, (consumer_tag, nowait), wait=None if nowait else spec.Basic.CancelOk, ) def _on_basic_cancel(self, consumer_tag): """Consumer cancelled by server. Most likely the queue was deleted. """ callback = self._remove_tag(consumer_tag) if callback: callback(consumer_tag) else: raise ConsumerCancelled(consumer_tag, spec.Basic.Cancel) def _on_basic_cancel_ok(self, consumer_tag): self._remove_tag(consumer_tag) def _remove_tag(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, argsig='BssbbbbF'): """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 acknowledgment 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. """ p = self.send_method( spec.Basic.Consume, argsig, (0, queue, consumer_tag, no_local, no_ack, exclusive, nowait, arguments), wait=None if nowait else spec.Basic.ConsumeOk, ) # XXX Fix this hack if not nowait and not consumer_tag: consumer_tag = p 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 p def _on_basic_deliver(self, consumer_tag, delivery_tag, redelivered, exchange, routing_key, msg): 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: AMQP_LOGGER.warning( REJECTED_MESSAGE_WITHOUT_CALLBACK, delivery_tag, consumer_tag, exchange, routing_key, ) self.basic_reject(delivery_tag, requeue=True) else: fun(msg) def basic_get(self, queue='', no_ack=False, argsig='Bsb'): """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 acknowledgment 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. """ ret = self.send_method( spec.Basic.Get, argsig, (0, queue, no_ack), wait=[spec.Basic.GetOk, spec.Basic.GetEmpty], returns_tuple=True, ) if not ret or len(ret) < 2: return self._on_get_empty(*ret) return self._on_get_ok(*ret) def _on_get_empty(self, cluster_id=None): pass def _on_get_ok(self, delivery_tag, redelivered, exchange, routing_key, message_count, msg): 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, timeout=None, argsig='Bssbb'): """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. """ if not self.connection: raise RecoverableConnectionError( 'basic_publish: connection closed') try: with self.connection.transport.having_timeout(timeout): return self.send_method( spec.Basic.Publish, argsig, (0, exchange, routing_key, mandatory, immediate), msg ) except socket.timeout: raise RecoverableChannelError('basic_publish: timed out') basic_publish = _basic_publish def basic_publish_confirm(self, *args, **kwargs): if not self._confirm_selected: self._confirm_selected = True self.confirm_select() ret = self._basic_publish(*args, **kwargs) self.wait(spec.Basic.Ack) return ret def basic_qos(self, prefetch_size, prefetch_count, a_global, argsig='lBb'): """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. """ return self.send_method( spec.Basic.Qos, argsig, (prefetch_size, prefetch_count, a_global), wait=spec.Basic.QosOk, ) 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. """ return self.send_method(spec.Basic.Recover, 'b', (requeue,)) def basic_recover_async(self, requeue=False): return self.send_method(spec.Basic.RecoverAsync, 'b', (requeue,)) def basic_reject(self, delivery_tag, requeue, argsig='Lb'): """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. """ return self.send_method( spec.Basic.Reject, argsig, (delivery_tag, requeue), ) def _on_basic_return(self, reply_code, reply_text, exchange, routing_key, message): """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. """ exc = error_for_code( reply_code, reply_text, spec.Basic.Return, ChannelError, ) handlers = self.events.get('basic_return') if not handlers: raise exc for callback in handlers: callback(exc, exchange, routing_key, message) ############# # # 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. """ return self.send_method(spec.Tx.Commit, wait=spec.Tx.CommitOk) 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. """ return self.send_method(spec.Tx.Rollback, wait=spec.Tx.RollbackOk) 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. """ return self.send_method(spec.Tx.Select, wait=spec.Tx.SelectOk) def confirm_select(self, nowait=False): """Enable publisher confirms for this channel. Note: This is 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. """ return self.send_method( spec.Confirm.Select, 'b', (nowait,), wait=None if nowait else spec.Confirm.SelectOk, ) def _on_basic_ack(self, delivery_tag, multiple): for callback in self.events['basic_ack']: callback(delivery_tag, multiple) amqp-2.2.2/amqp/transport.py0000644000175000017500000003556513154420412015757 0ustar omeromer00000000000000"""Transport implementation.""" # Copyright (C) 2009 Barry Pederson from __future__ import absolute_import, unicode_literals import errno import re import socket import ssl from contextlib import contextmanager from .exceptions import UnexpectedFrame from .five import items from .platform import ( SOL_TCP, TCP_USER_TIMEOUT, HAS_TCP_USER_TIMEOUT, HAS_TCP_MAXSEG, pack, unpack, ) from .utils import get_errno, set_cloexec try: from ssl import SSLError except ImportError: # pragma: no cover class SSLError(Exception): # noqa """Dummy SSL exception.""" _UNAVAIL = {errno.EAGAIN, errno.EINTR, errno.ENOENT, errno.EWOULDBLOCK} AMQP_PORT = 5672 EMPTY_BUFFER = bytes() SIGNED_INT_MAX = 0x7FFFFFFF # 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+))?') # available socket options for TCP level KNOWN_TCP_OPTS = ( 'TCP_CORK', 'TCP_DEFER_ACCEPT', 'TCP_KEEPCNT', 'TCP_KEEPIDLE', 'TCP_KEEPINTVL', 'TCP_LINGER2', 'TCP_NODELAY', 'TCP_QUICKACK', 'TCP_SYNCNT', 'TCP_WINDOW_CLAMP', ) if HAS_TCP_MAXSEG: KNOWN_TCP_OPTS += ('TCP_MAXSEG',) TCP_OPTS = { getattr(socket, opt) for opt in KNOWN_TCP_OPTS if hasattr(socket, opt) } DEFAULT_SOCKET_SETTINGS = { socket.TCP_NODELAY: 1, } if HAS_TCP_USER_TIMEOUT: KNOWN_TCP_OPTS += ('TCP_USER_TIMEOUT',) TCP_OPTS.add(TCP_USER_TIMEOUT) DEFAULT_SOCKET_SETTINGS[TCP_USER_TIMEOUT] = 1000 try: from socket import TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_KEEPCNT # noqa except ImportError: pass else: DEFAULT_SOCKET_SETTINGS.update({ TCP_KEEPIDLE: 60, TCP_KEEPINTVL: 10, TCP_KEEPCNT: 9, }) def to_host_port(host, default=AMQP_PORT): """Convert hostname:port string to host, port tuple.""" port = default 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) return host, port class _AbstractTransport(object): """Common superclass for TCP and SSL transports.""" connected = False def __init__(self, host, connect_timeout=None, read_timeout=None, write_timeout=None, socket_settings=None, raise_on_initial_eintr=True, **kwargs): self.connected = True self.sock = None self.raise_on_initial_eintr = raise_on_initial_eintr self._read_buffer = EMPTY_BUFFER self.host, self.port = to_host_port(host) self.connect_timeout = connect_timeout self.read_timeout = read_timeout self.write_timeout = write_timeout self.socket_settings = socket_settings def connect(self): self._connect(self.host, self.port, self.connect_timeout) self._init_socket( self.socket_settings, self.read_timeout, self.write_timeout, ) @contextmanager def having_timeout(self, timeout): if timeout is None: yield self.sock else: sock = self.sock prev = sock.gettimeout() if prev != timeout: sock.settimeout(timeout) try: yield self.sock except SSLError as exc: if 'timed out' in str(exc): # http://bugs.python.org/issue10272 raise socket.timeout() elif 'The operation did not complete' in str(exc): # Non-blocking SSL sockets can throw SSLError raise socket.timeout() raise finally: if timeout != prev: sock.settimeout(prev) def _connect(self, host, port, timeout): entries = socket.getaddrinfo( host, port, 0, socket.SOCK_STREAM, SOL_TCP, ) for i, res in enumerate(entries): 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(timeout) self.sock.connect(sa) except socket.error: self.sock.close() self.sock = None if i + 1 >= len(entries): raise else: break def _init_socket(self, socket_settings, read_timeout, write_timeout): try: self.sock.settimeout(None) # set socket back to blocking mode self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) self._set_socket_options(socket_settings) # set socket timeouts for timeout, interval in ((socket.SO_SNDTIMEO, write_timeout), (socket.SO_RCVTIMEO, read_timeout)): if interval is not None: self.sock.setsockopt( socket.SOL_SOCKET, timeout, pack('ll', interval, 0), ) 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 _get_tcp_socket_defaults(self, sock): return { opt: sock.getsockopt(SOL_TCP, opt) for opt in TCP_OPTS } def _set_socket_options(self, socket_settings): tcp_opts = self._get_tcp_socket_defaults(self.sock) final_socket_settings = dict(DEFAULT_SOCKET_SETTINGS) if socket_settings: final_socket_settings.update(socket_settings) tcp_opts.update(final_socket_settings) for opt, val in items(tcp_opts): self.sock.setsockopt(SOL_TCP, opt, val) 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.""" 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) # >I is an unsigned int, but the argument to sock.recv is signed, # so we know the size can be at most 2 * SIGNED_INT_MAX if size > SIGNED_INT_MAX: part1 = read(SIGNED_INT_MAX) part2 = read(size - SIGNED_INT_MAX) payload = ''.join([part1, part2]) else: 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, SSLError, 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 {0:#04x} while expecting 0xce'.format(ch)) def write(self, s): try: self._write(s) 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=None, ssl=None, **kwargs): self.sslopts = ssl if isinstance(ssl, dict) else {} self._read_buffer = EMPTY_BUFFER super(SSLTransport, self).__init__( host, connect_timeout=connect_timeout, **kwargs) def _setup_transport(self): """Wrap the socket in an SSL object.""" self.sock = self._wrap_socket(self.sock, **self.sslopts) self.sock.do_handshake() self._quick_recv = self.sock.read def _wrap_socket(self, sock, context=None, **sslopts): if context: return self._wrap_context(sock, sslopts, **context) return self._wrap_socket_sni(sock, **sslopts) def _wrap_context(self, sock, sslopts, check_hostname=None, **ctx_options): ctx = ssl.create_default_context(**ctx_options) ctx.check_hostname = check_hostname return ctx.wrap_socket(sock, **sslopts) def _wrap_socket_sni(self, sock, keyfile=None, certfile=None, server_side=False, cert_reqs=ssl.CERT_NONE, ca_certs=None, do_handshake_on_connect=True, suppress_ragged_eofs=True, server_hostname=None, ciphers=None, ssl_version=None): """Socket wrap with SNI headers. Default `ssl.wrap_socket` method augmented with support for setting the server_hostname field required for SNI hostname header """ opts = dict(sock=sock, keyfile=keyfile, certfile=certfile, server_side=server_side, cert_reqs=cert_reqs, ca_certs=ca_certs, do_handshake_on_connect=do_handshake_on_connect, suppress_ragged_eofs=suppress_ragged_eofs, ciphers=ciphers) # Setup the right SSL version; default to optimal versions across # ssl implementations if ssl_version is not None: opts['ssl_version'] = ssl_version else: # older versions of python 2.7 and python 2.6 do not have the # ssl.PROTOCOL_TLS defined the equivalent is ssl.PROTOCOL_SSLv23 # we default to PROTOCOL_TLS and fallback to PROTOCOL_SSLv23 if hasattr(ssl, 'PROTOCOL_TLS'): opts['ssl_version'] = ssl.PROTOCOL_TLS else: opts['ssl_version'] = ssl.PROTOCOL_SSLv23 # Set SNI headers if supported if (server_hostname is not None) and ( hasattr(ssl, 'HAS_SNI') and ssl.HAS_SNI): opts['server_hostname'] = server_hostname sock = ssl.SSLSocket(**opts) return sock 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 exc.errno in _errnos: if initial and self.raise_on_initial_eintr: raise socket.timeout() 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.""" write = self.sock.write while s: try: n = write(s) except (ValueError, AttributeError): # AG: sock._sslobj might become null in the meantime if the # remote connection has hung up. # In python 3.2, an AttributeError is raised because the SSL # module tries to access self._sslobj.write (w/ self._sslobj == # None) # In python 3.4, a ValueError is raised is self._sslobj is # None. So much for portability... :/ n = 0 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 exc.errno in _errnos: if initial and self.raise_on_initial_eintr: raise socket.timeout() 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 Transport(host, connect_timeout=None, ssl=False, **kwargs): """Create transport. Given a few parameters from the Connection constructor, select and create a subclass of _AbstractTransport. """ transport = SSLTransport if ssl else TCPTransport return transport(host, connect_timeout=connect_timeout, ssl=ssl, **kwargs) amqp-2.2.2/amqp/basic_message.py0000644000175000017500000000640213154420412016474 0ustar omeromer00000000000000"""AMQP Messages.""" # Copyright (C) 2007-2008 Barry Pederson from __future__ import absolute_import, unicode_literals # Intended to fix #85: ImportError: cannot import name spec # Encountered on python 2.7.3 # "The submodules often need to refer to each other. For example, the # surround [sic] module might use the echo module. In fact, such # references are so common that the import statement first looks in # the containing package before looking in the standard module search # path." # Source: # http://stackoverflow.com/a/14216937/4982251 from .spec import Basic from .serialization import GenericContent __all__ = ['Message'] class Message(GenericContent): """A Message for use with the Channel.basic_* methods. 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}) """ CLASS_ID = Basic.CLASS_ID #: Instances of this class have these attributes, which #: are passed back and forth as message properties between #: client and server PROPERTIES = [ ('content_type', 's'), ('content_encoding', 's'), ('application_headers', 'F'), ('delivery_mode', 'o'), ('priority', 'o'), ('correlation_id', 's'), ('reply_to', 's'), ('expiration', 's'), ('message_id', 's'), ('timestamp', 'L'), ('type', 's'), ('user_id', 's'), ('app_id', 's'), ('cluster_id', 's') ] #: set by basic_consume/basic_get delivery_info = None def __init__(self, body='', children=None, channel=None, **properties): super(Message, self).__init__(**properties) self.body = body self.channel = channel @property def headers(self): return self.properties.get('application_headers') @property def delivery_tag(self): return self.delivery_info.get('delivery_tag') amqp-2.2.2/amqp/sasl.py0000644000175000017500000001213013131450043014642 0ustar omeromer00000000000000"""SASL mechanisms for AMQP authentication.""" from __future__ import absolute_import, unicode_literals from io import BytesIO import socket import warnings from amqp.serialization import _write_table class SASL(object): """The base class for all amqp SASL authentication mechanisms. You should sub-class this if you're implementing your own authentication. """ @property def mechanism(self): """Return a bytes containing the SASL mechanism name.""" raise NotImplementedError def start(self, connection): """Return the first response to a SASL challenge as a bytes object.""" raise NotImplementedError class PLAIN(SASL): """PLAIN SASL authentication mechanism. See https://tools.ietf.org/html/rfc4616 for details """ mechanism = b'PLAIN' def __init__(self, username, password): self.username, self.password = username, password def start(self, connection): login_response = BytesIO() login_response.write(b'\0') login_response.write(self.username.encode('utf-8')) login_response.write(b'\0') login_response.write(self.password.encode('utf-8')) return login_response.getvalue() class AMQPLAIN(SASL): """AMQPLAIN SASL authentication mechanism. This is a non-standard mechanism used by AMQP servers. """ mechanism = b'AMQPLAIN' def __init__(self, username, password): self.username, self.password = username, password def start(self, connection): login_response = BytesIO() _write_table({b'LOGIN': self.username, b'PASSWORD': self.password}, login_response.write, []) # Skip the length at the beginning return login_response.getvalue()[4:] def _get_gssapi_mechanism(): try: import gssapi except ImportError: class FakeGSSAPI(SASL): """A no-op SASL mechanism for when gssapi isn't available.""" mechanism = None def __init__(self, client_name=None, service=b'amqp', rdns=False, fail_soft=False): if not fail_soft: raise NotImplementedError( "You need to install the `gssapi` module for GSSAPI " "SASL support") def start(self): # pragma: no cover return NotImplemented return FakeGSSAPI else: import gssapi.raw.misc class GSSAPI(SASL): """GSSAPI SASL authentication mechanism. See https://tools.ietf.org/html/rfc4752 for details """ mechanism = b'GSSAPI' def __init__(self, client_name=None, service=b'amqp', rdns=False, fail_soft=False): if client_name and not isinstance(client_name, bytes): client_name = client_name.encode('ascii') self.client_name = client_name self.fail_soft = fail_soft self.service = service self.rdns = rdns def get_hostname(self, connection): sock = connection.transport.sock if self.rdns and sock.family in (socket.AF_INET, socket.AF_INET6): peer = sock.getpeername() hostname, _, _ = socket.gethostbyaddr(peer[0]) else: hostname = connection.transport.host if not isinstance(hostname, bytes): hostname = hostname.encode('ascii') return hostname def start(self, connection): try: if self.client_name: creds = gssapi.Credentials( name=gssapi.Name(self.client_name)) else: creds = None hostname = self.get_hostname(connection) name = gssapi.Name(b'@'.join([self.service, hostname]), gssapi.NameType.hostbased_service) context = gssapi.SecurityContext(name=name, creds=creds) return context.step(None) except gssapi.raw.misc.GSSError: if self.fail_soft: return NotImplemented else: raise return GSSAPI GSSAPI = _get_gssapi_mechanism() class RAW(SASL): """A generic custom SASL mechanism. This mechanism takes a mechanism name and response to send to the server, so can be used for simple custom authentication schemes. """ mechanism = None def __init__(self, mechanism, response): assert isinstance(mechanism, bytes) assert isinstance(response, bytes) self.mechanism, self.response = mechanism, response warnings.warn("Passing login_method and login_response to Connection " "is deprecated. Please implement a SASL subclass " "instead.", DeprecationWarning) def start(self, connection): return self.response amqp-2.2.2/extra/0000755000175000017500000000000013156466127013537 5ustar omeromer00000000000000amqp-2.2.2/extra/update_comments_from_spec.py0000644000175000017500000000357413131450043021325 0ustar omeromer00000000000000from __future__ import absolute_import, unicode_literals 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())