From 735438a6120257aaa19222e0657bc07407721875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFs=20Burg?= Date: Tue, 10 Jul 2018 15:52:50 +0200 Subject: [PATCH 01/72] setup.py: Add embedded classifier --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index b83b136..366a721 100644 --- a/setup.py +++ b/setup.py @@ -37,6 +37,7 @@ "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", + "Topic :: Software Development :: Embedded Systems", ], platforms='all', license='BSD' From 571f497729f89f32eff841f332c2ab3c3c128210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFs=20Burg?= Date: Mon, 16 Jul 2018 10:07:50 +0200 Subject: [PATCH 02/72] Revert "setup.py: Add embedded classifier" This reverts commit 735438a6120257aaa19222e0657bc07407721875. --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 366a721..b83b136 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,6 @@ "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", - "Topic :: Software Development :: Embedded Systems", ], platforms='all', license='BSD' From 45bcefc7514db5bf0fb2c5a8a670ba7dc4d43f33 Mon Sep 17 00:00:00 2001 From: Mads Sejersen Date: Fri, 28 Sep 2018 13:22:37 +0200 Subject: [PATCH 03/72] Allow publishing messages with an empty payload --- aioamqp/channel.py | 2 -- aioamqp/tests/test_publish.py | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/aioamqp/channel.py b/aioamqp/channel.py index fa443f1..bc1b0ef 100644 --- a/aioamqp/channel.py +++ b/aioamqp/channel.py @@ -476,7 +476,6 @@ def queue_purge_ok(self, frame): @asyncio.coroutine def basic_publish(self, payload, exchange_name, routing_key, properties=None, mandatory=False, immediate=False): - assert payload, "Payload cannot be empty" if isinstance(payload, str): warnings.warn("Str payload support will be removed in next release", DeprecationWarning) payload = payload.encode() @@ -816,7 +815,6 @@ def basic_return(self, frame): @asyncio.coroutine def publish(self, payload, exchange_name, routing_key, properties=None, mandatory=False, immediate=False): - assert payload, "Payload cannot be empty" if isinstance(payload, str): warnings.warn("Str payload support will be removed in next release", DeprecationWarning) payload = payload.encode() diff --git a/aioamqp/tests/test_publish.py b/aioamqp/tests/test_publish.py index 8fb5e47..47de887 100644 --- a/aioamqp/tests/test_publish.py +++ b/aioamqp/tests/test_publish.py @@ -23,6 +23,21 @@ def test_publish(self): self.assertIn("q", queues) self.assertEqual(1, queues["q"]['messages']) + @testing.coroutine + def test_empty_publish(self): + # declare + yield from self.channel.queue_declare("q", exclusive=True, no_wait=False) + yield from self.channel.exchange_declare("e", "fanout") + yield from self.channel.queue_bind("q", "e", routing_key='') + + # publish + yield from self.channel.publish("", "e", routing_key='') + + queues = self.list_queues() + self.assertIn("q", queues) + self.assertEqual(1, queues["q"]["messages"]) + self.assertEqual(0, queues["q"]["message_bytes"]) + @testing.coroutine def test_big_publish(self): # declare From 4300f2eaf0f4e3bf2b326227672c6ee3344f9d27 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Mon, 1 Oct 2018 10:35:42 +0200 Subject: [PATCH 04/72] adds Mads Sejersen to AUTHORS --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 8574469..4893c13 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -19,4 +19,5 @@ AUTHORS are (and/or have been):: * Alexander Gromyko * Nick Humrich * Pavel Kamaev + * Mads Sejersen From e6907a0f39cc1ceb80b6308cd5e28af038d98bb1 Mon Sep 17 00:00:00 2001 From: Dave Shawley Date: Tue, 2 Oct 2018 07:06:16 -0400 Subject: [PATCH 05/72] Fix test_socket_nodelay on macOS. TCP_NODELAY isn't guaranteed to be zero or one. The guarantee is zero or non-zero so the test should test that. For some reason I was consistently getting a value of 4 on my MacBookPro. --- aioamqp/tests/test_connect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aioamqp/tests/test_connect.py b/aioamqp/tests/test_connect.py index 210453d..f7a52ff 100644 --- a/aioamqp/tests/test_connect.py +++ b/aioamqp/tests/test_connect.py @@ -51,5 +51,5 @@ def test_socket_nodelay(self): transport, proto = yield from connect(virtualhost=self.vhost, loop=self.loop) sock = transport.get_extra_info('socket') opt_val = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) - self.assertEqual(opt_val, 1) + self.assertNotEqual(opt_val, 0) yield from proto.close() From e201d66c696a9caf3978f8e5156d306589581aa8 Mon Sep 17 00:00:00 2001 From: Dave Shawley Date: Wed, 3 Oct 2018 07:43:49 -0400 Subject: [PATCH 06/72] Add consumer cancellation callback. --- aioamqp/channel.py | 19 ++++++++ aioamqp/tests/test_server_basic_cancel.py | 59 +++++++++++++++++++++-- docs/api.rst | 26 ++++++++++ docs/changelog.rst | 2 + 4 files changed, 102 insertions(+), 4 deletions(-) diff --git a/aioamqp/channel.py b/aioamqp/channel.py index bc1b0ef..bd8f2c7 100644 --- a/aioamqp/channel.py +++ b/aioamqp/channel.py @@ -26,6 +26,7 @@ def __init__(self, protocol, channel_id, return_callback=None): self.channel_id = channel_id self.consumer_queues = {} self.consumer_callbacks = {} + self.cancellation_callbacks = [] self.return_callback = return_callback self.response_future = None self.close_event = asyncio.Event(loop=self._loop) @@ -650,6 +651,12 @@ def server_basic_cancel(self, frame): _no_wait = frame.payload_decoder.read_bit() self.cancelled_consumers.add(consumer_tag) logger.info("consume cancelled received") + for callback in self.cancellation_callbacks: + try: + yield from callback(self, consumer_tag) + except Exception as error: # pylint: disable=broad-except + logger.error("cancellation callback %r raised exception %r", + callback, error) @asyncio.coroutine def basic_cancel(self, consumer_tag, no_wait=False): @@ -879,3 +886,15 @@ def confirm_select_ok(self, frame): fut = self._get_waiter('confirm_select') fut.set_result(True) logger.debug("Confirm selected") + + def add_cancellation_callback(self, callback): + """Add a callback that is invoked when a consumer is cancelled. + + :param callback: function to call + + `callback` is called with the channel and consumer tag as positional + parameters. The callback can be either a plain callable or an + asynchronous co-routine. + + """ + self.cancellation_callbacks.append(callback) diff --git a/aioamqp/tests/test_server_basic_cancel.py b/aioamqp/tests/test_server_basic_cancel.py index 8548620..71c5c46 100644 --- a/aioamqp/tests/test_server_basic_cancel.py +++ b/aioamqp/tests/test_server_basic_cancel.py @@ -3,21 +3,72 @@ """ -import unittest +import asyncio +import unittest.mock +import uuid from . import testcase from . import testing +@asyncio.coroutine +def consumer(channel, body, envelope, properties): + yield from channel.basic_client_ack(envelope.delivery_tag) + + class ServerBasicCancelTestCase(testcase.RabbitTestCase, unittest.TestCase): _multiprocess_can_split_ = True + def setUp(self): + super().setUp() + self.queue_name = str(uuid.uuid4()) + @testing.coroutine def test_cancel_whilst_consuming(self): - queue_name = 'queue_name' - yield from self.channel.queue_declare(queue_name) + yield from self.channel.queue_declare(self.queue_name) # None is non-callable. We want to make sure the callback is # unregistered and never called. yield from self.channel.basic_consume(None) - yield from self.channel.queue_delete(queue_name) + yield from self.channel.queue_delete(self.queue_name) + + @testing.coroutine + def test_cancel_callbacks(self): + callback_calls = [] + + @asyncio.coroutine + def coroutine_callback(*args, **kwargs): + callback_calls.append((args, kwargs)) + + def function_callback(*args, **kwargs): + callback_calls.append((args, kwargs)) + + self.channel.add_cancellation_callback(coroutine_callback) + self.channel.add_cancellation_callback(function_callback) + + yield from self.channel.queue_declare(self.queue_name) + rv = yield from self.channel.basic_consume(consumer) + yield from self.channel.queue_delete(self.queue_name) + + self.assertEqual(2, len(callback_calls)) + for args, kwargs in callback_calls: + self.assertIs(self.channel, args[0]) + self.assertEqual(rv['consumer_tag'], args[1]) + + @testing.coroutine + def test_cancel_callback_exceptions(self): + callback_calls = [] + + def function_callback(*args, **kwargs): + callback_calls.append((args, kwargs)) + raise RuntimeError + + self.channel.add_cancellation_callback(function_callback) + self.channel.add_cancellation_callback(function_callback) + + yield from self.channel.queue_declare(self.queue_name) + yield from self.channel.basic_consume(consumer) + yield from self.channel.queue_delete(self.queue_name) + + self.assertEqual(2, len(callback_calls)) + self.assertTrue(self.channel.is_open) diff --git a/docs/api.rst b/docs/api.rst index 5d5380e..31ee506 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -180,7 +180,33 @@ In the callback: app_id cluster_id +Server Cancellation +~~~~~~~~~~~~~~~~~~~ +RabbitMQ offers an AMQP extension to notify a consumer when a queue is deleted. +See `Consumer Cancel Notification `_ +for additional details. ``aioamqp`` enables the extension for all channels but +takes no action when the consumer is cancelled. Your application can be notified +of consumer cancellations by adding a callback to the channel:: + + @asyncio.coroutine + def consumer_cancelled(channel, consumer_tag): + # implement required cleanup here + pass + + + @asyncio.coroutine + def consumer(channel, body, envelope, properties): + channel.basic_ack(envelope.delivery_tag) + + + channel = yield from protocol.channel() + channel.add_cancellation_callback(consumer_cancelled) + yield from channel.basic_consume(consumer, queue_name="my_queue") + +The callback can be a simple callable or an asynchronous co-routine. It can +be used to restart consumption on the channel, close the channel, or anything +else that is appropriate for your application. Queues ------ diff --git a/docs/changelog.rst b/docs/changelog.rst index 3cae518..3f57989 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,8 @@ Changelog Next release ------------ + * Call user-specified callback when a consumer is cancelled. + Aioamqp 0.11.0 -------------- From bf610d2b87ced9134bdff5aa4758945092a9487f Mon Sep 17 00:00:00 2001 From: Dave Shawley Date: Wed, 3 Oct 2018 07:49:54 -0400 Subject: [PATCH 07/72] Ensure that sphinx is installed in Makefile. --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 45a1782..1a4be49 100644 --- a/Makefile +++ b/Makefile @@ -22,9 +22,11 @@ SPHINXBUILDDIR ?= $(BUILD_DIR)/sphinx/html ALLSPHINXOPTS ?= -d $(BUILD_DIR)/sphinx/doctrees $(SPHINXOPTS) docs doc: + @ pip install -q Sphinx sphinx-rtd-theme sphinx-build -a $(INPUT_DIR) build livehtml: docs + @ pip install -q Sphinx sphinx-rtd-theme sphinx-autobuild $(AUTOSPHINXOPTS) $(ALLSPHINXOPTS) $(SPHINXBUILDDIR) test: From 3bfc963d393e3a08bd6fa99bf097f09fdea2d2d9 Mon Sep 17 00:00:00 2001 From: Dave Shawley Date: Thu, 4 Oct 2018 06:54:28 -0400 Subject: [PATCH 08/72] Move sphinx requirements in requirements_dev. --- Makefile | 2 -- requirements_dev.txt | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 1a4be49..45a1782 100644 --- a/Makefile +++ b/Makefile @@ -22,11 +22,9 @@ SPHINXBUILDDIR ?= $(BUILD_DIR)/sphinx/html ALLSPHINXOPTS ?= -d $(BUILD_DIR)/sphinx/doctrees $(SPHINXOPTS) docs doc: - @ pip install -q Sphinx sphinx-rtd-theme sphinx-build -a $(INPUT_DIR) build livehtml: docs - @ pip install -q Sphinx sphinx-rtd-theme sphinx-autobuild $(AUTOSPHINXOPTS) $(ALLSPHINXOPTS) $(SPHINXBUILDDIR) test: diff --git a/requirements_dev.txt b/requirements_dev.txt index abc0a2a..02eaa1e 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -3,4 +3,6 @@ nose coverage pylint +Sphinx +sphinx-rtd-theme -e git+https://github.com/bkjones/pyrabbit.git#egg=pyrabbit From 003115fb679df2e9b46a3243899c108f8019a61a Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Mon, 8 Oct 2018 17:25:22 +0200 Subject: [PATCH 09/72] adds Dave Shawley to AUTHORS file --- AUTHORS.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 4893c13..72e66af 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -20,4 +20,4 @@ AUTHORS are (and/or have been):: * Nick Humrich * Pavel Kamaev * Mads Sejersen - + * Dave Shawley From ead626ec38254ab619207f8be9dbfbf3fea25f1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Codet?= Date: Mon, 12 Nov 2018 12:09:03 +0100 Subject: [PATCH 10/72] tests: use logger.exception as intended logger.error with `exc_info=True` is a logger.exception https://github.com/python/cpython/blob/130893debfd97c70e3a89d9ba49892f53e6b9d79/Lib/logging/__init__.py#L1472-L1476 --- aioamqp/tests/testcase.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aioamqp/tests/testcase.py b/aioamqp/tests/testcase.py index a377110..a554f60 100644 --- a/aioamqp/tests/testcase.py +++ b/aioamqp/tests/testcase.py @@ -210,7 +210,7 @@ def safe_queue_delete(self, queue_name, channel=None): except asyncio.TimeoutError: logger.warning('Timeout on queue %s deletion', full_queue_name, exc_info=True) except Exception: # pylint: disable=broad-except - logger.error('Unexpected error on queue %s deletion', full_queue_name, exc_info=True) + logger.exception('Unexpected error on queue %s deletion', full_queue_name) @asyncio.coroutine def safe_exchange_delete(self, exchange_name, channel=None): @@ -225,7 +225,7 @@ def safe_exchange_delete(self, exchange_name, channel=None): except asyncio.TimeoutError: logger.warning('Timeout on exchange %s deletion', full_exchange_name, exc_info=True) except Exception: # pylint: disable=broad-except - logger.error('Unexpected error on exchange %s deletion', full_exchange_name, exc_info=True) + logger.exception('Unexpected error on exchange %s deletion', full_exchange_name) def full_name(self, name): if self.is_full_name(name): From 2e0c46a51971f2bad1d0075965f9b747ab021459 Mon Sep 17 00:00:00 2001 From: Jacob Hagstedt Date: Wed, 28 Nov 2018 15:51:07 +0100 Subject: [PATCH 11/72] Add check for bit length used by frame int value to write long-long-int --- aioamqp/frame.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/aioamqp/frame.py b/aioamqp/frame.py index 7240ec1..7b76c8f 100644 --- a/aioamqp/frame.py +++ b/aioamqp/frame.py @@ -92,8 +92,12 @@ def write_value(self, value): self.payload.write(b'F') self.write_table(value) elif isinstance(value, int): - self.payload.write(b'I') - self.write_long(value) + if value.bit_length() >= 32: + self.payload.write(b'L') + self.write_long_long(value) + else: + self.payload.write(b'I') + self.write_long(value) elif isinstance(value, float): self.payload.write(b'd') self.write_float(value) From 85a7bb9a4c735c37b9501f977abf34583d56b55e Mon Sep 17 00:00:00 2001 From: Jacob Hagstedt Date: Wed, 28 Nov 2018 16:47:44 +0100 Subject: [PATCH 12/72] Add test for long-long-int x-message-ttl value --- aioamqp/tests/test_queue.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/aioamqp/tests/test_queue.py b/aioamqp/tests/test_queue.py index de7a553..47b1965 100644 --- a/aioamqp/tests/test_queue.py +++ b/aioamqp/tests/test_queue.py @@ -50,6 +50,20 @@ def test_queue_declare_passive(self): self.assertEqual(result['consumer_count'], 0) self.assertEqual(result['queue'].split('.')[-1], queue_name) + @testing.coroutine + def test_queue_declare_custom_x_message_ttl_32_bits(self): + queue_name = 'queue_name' + # 2147483648 == 10000000000000000000000000000000 + # in binary, meaning it is 32 bit long + x_message_ttl = 2147483648 + result = yield from self.channel.queue_declare('queue_name', arguments={ + 'x-message-ttl': x_message_ttl + }) + self.assertEqual(result['message_count'], 0) + self.assertEqual(result['consumer_count'], 0) + self.assertEqual(result['queue'].split('.')[-1], queue_name) + self.assertTrue(result) + @testing.coroutine def test_queue_declare_passive_nonexistant_queue(self): queue_name = 'queue_name' From 5cb1efadb9063ae186a3659781df4ef4e8df8ed4 Mon Sep 17 00:00:00 2001 From: Jacob Hagstedt Date: Wed, 28 Nov 2018 16:48:09 +0100 Subject: [PATCH 13/72] Fix host and port to use the ENV --- aioamqp/tests/test_connect.py | 6 ++++-- aioamqp/tests/test_protocol.py | 8 +++++--- aioamqp/tests/testcase.py | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/aioamqp/tests/test_connect.py b/aioamqp/tests/test_connect.py index f7a52ff..9ec122f 100644 --- a/aioamqp/tests/test_connect.py +++ b/aioamqp/tests/test_connect.py @@ -13,7 +13,7 @@ class AmqpConnectionTestCase(testcase.RabbitTestCase, unittest.TestCase): @testing.coroutine def test_connect(self): - _transport, proto = yield from connect(virtualhost=self.vhost, loop=self.loop) + _transport, proto = yield from connect(host=self.host, port=self.port, virtualhost=self.vhost, loop=self.loop) self.assertEqual(proto.state, OPEN) self.assertIsNotNone(proto.server_properties) yield from proto.close() @@ -25,6 +25,8 @@ def test_connect_tuning(self): channel_max = 10 heartbeat = 100 _transport, proto = yield from connect( + host=self.host, + port=self.port, virtualhost=self.vhost, loop=self.loop, channel_max=channel_max, @@ -48,7 +50,7 @@ def test_connect_tuning(self): @testing.coroutine def test_socket_nodelay(self): - transport, proto = yield from connect(virtualhost=self.vhost, loop=self.loop) + transport, proto = yield from connect(host=self.host, port=self.port, virtualhost=self.vhost, loop=self.loop) sock = transport.get_extra_info('socket') opt_val = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) self.assertNotEqual(opt_val, 0) diff --git a/aioamqp/tests/test_protocol.py b/aioamqp/tests/test_protocol.py index 651d875..c0e1ec4 100644 --- a/aioamqp/tests/test_protocol.py +++ b/aioamqp/tests/test_protocol.py @@ -19,7 +19,7 @@ class ProtocolTestCase(testcase.RabbitTestCase, unittest.TestCase): @testing.coroutine def test_connect(self): - _transport, protocol = yield from amqp_connect(virtualhost=self.vhost, loop=self.loop) + _transport, protocol = yield from amqp_connect(host=self.host, port=self.port, virtualhost=self.vhost, loop=self.loop) self.assertEqual(protocol.state, OPEN) yield from protocol.close() @@ -30,6 +30,8 @@ def test_connect_products_info(self): 'program_version': '0.1.1', } _transport, protocol = yield from amqp_connect( + host=self.host, + port=self.port, virtualhost=self.vhost, client_properties=client_properties, loop=self.loop, @@ -41,11 +43,11 @@ def test_connect_products_info(self): @testing.coroutine def test_connection_unexistant_vhost(self): with self.assertRaises(exceptions.AmqpClosedConnection): - yield from amqp_connect(virtualhost='/unexistant', loop=self.loop) + yield from amqp_connect(host=self.host, port=self.port, virtualhost='/unexistant', loop=self.loop) def test_connection_wrong_login_password(self): with self.assertRaises(exceptions.AmqpClosedConnection): - self.loop.run_until_complete(amqp_connect(login='wrong', password='wrong', loop=self.loop)) + self.loop.run_until_complete(amqp_connect(host=self.host, port=self.port, login='wrong', password='wrong', loop=self.loop)) @testing.coroutine def test_connection_from_url(self): diff --git a/aioamqp/tests/testcase.py b/aioamqp/tests/testcase.py index a554f60..e8f0a2d 100644 --- a/aioamqp/tests/testcase.py +++ b/aioamqp/tests/testcase.py @@ -85,7 +85,7 @@ def setUp(self): self.port = os.environ.get('AMQP_PORT', 5672) self.vhost = os.environ.get('AMQP_VHOST', self.VHOST + str(uuid.uuid4())) self.http_client = pyrabbit.api.Client( - 'localhost:15672/api/', 'guest', 'guest', timeout=20 + '{HOST}:15672/api/'.format(HOST=self.host), 'guest', 'guest', timeout=20 ) self.amqps = [] From 0b5d4fe76ef423b6780b8e453f4b5caca36f9d2d Mon Sep 17 00:00:00 2001 From: Jacob Hagstedt Date: Wed, 28 Nov 2018 16:48:32 +0100 Subject: [PATCH 14/72] Add Dockerfile and docker-compose to run tests using Docker --- Dockerfile | 7 +++++++ README.rst | 6 ++++++ docker-compose.yaml | 17 +++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 Dockerfile create mode 100644 docker-compose.yaml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7ec4545 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.5 + +WORKDIR /usr/src/app + +COPY . . + +RUN pip install -r requirements_dev.txt diff --git a/README.rst b/README.rst index f8bc147..90d66f1 100644 --- a/README.rst +++ b/README.rst @@ -30,6 +30,12 @@ Tests require an instance of RabbitMQ. You can start a new instance using docker Then you can run the tests with ``make test`` (requires ``nose``). +tests using docker-compose +^^^^^^^^^^^^^^^^^^^^^^^^^^ +Start RabbitMQ using ``docker-compose up -d rabbitmq``. When RabbitMQ has started, start the tests using ``docker-compose up --build aioamqp-test`` + + + .. _AMQP 0.9.1 protocol: https://www.rabbitmq.com/amqp-0-9-1-quickref.html .. _PEP 3156: http://www.python.org/dev/peps/pep-3156/ diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..f9d4994 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,17 @@ +version: '3' +services: + aioamqp-test: + build: . + command: ["make", "test"] + depends_on: + - rabbitmq + environment: + - AMQP_HOST=rabbitmq + rabbitmq: + hostname: rabbitmq + image: rabbitmq:3-management + environment: + - RABBITMQ_NODENAME=my-rabbit + ports: + - 15672 + - 5672 From f908ec66d2525bd758c2b1f7ba6eb66fa5e4b4b5 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Fri, 30 Nov 2018 17:37:52 +0100 Subject: [PATCH 15/72] add Jacob Hagstedt P Suorra to AUTHORS file --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 72e66af..7799e39 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -21,3 +21,4 @@ AUTHORS are (and/or have been):: * Pavel Kamaev * Mads Sejersen * Dave Shawley + * Jacob Hagstedt P Suorra From cb3a6dedfc479885567a9d65f7dd7cb5045967ff Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Fri, 30 Nov 2018 17:38:01 +0100 Subject: [PATCH 16/72] Add changelog --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 3f57989..922a74d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,7 @@ Changelog Next release ------------ + * Fix an issue to use correct int encoder depending on int size (closes #180). * Call user-specified callback when a consumer is cancelled. Aioamqp 0.11.0 From aac75d2d3b024d0634775822846cde571f0e2f88 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Fri, 30 Nov 2018 17:44:20 +0100 Subject: [PATCH 17/72] Release version v0.12.0 --- aioamqp/version.py | 2 +- docs/changelog.rst | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/aioamqp/version.py b/aioamqp/version.py index 4faaee9..412a111 100644 --- a/aioamqp/version.py +++ b/aioamqp/version.py @@ -1,2 +1,2 @@ -__version__ = '0.11.0' +__version__ = '0.12.0' __packagename__ = 'aioamqp' diff --git a/docs/changelog.rst b/docs/changelog.rst index 922a74d..665c809 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,9 @@ Changelog Next release ------------ +Aioamqp 0.12.0 +-------------- + * Fix an issue to use correct int encoder depending on int size (closes #180). * Call user-specified callback when a consumer is cancelled. From 4e841dcbf3cec5c7df574f06d70a4ebdde522391 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Mon, 10 Dec 2018 17:58:42 +0100 Subject: [PATCH 18/72] tests: switch from pyrabbit to pyrabbit2 pyrabbit is now unmaintained --- aioamqp/tests/testcase.py | 6 +++--- requirements_dev.txt | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/aioamqp/tests/testcase.py b/aioamqp/tests/testcase.py index e8f0a2d..3be37cf 100644 --- a/aioamqp/tests/testcase.py +++ b/aioamqp/tests/testcase.py @@ -10,7 +10,7 @@ import time import uuid -import pyrabbit.api +import pyrabbit2.api from . import testing from .. import connect as aioamqp_connect @@ -84,8 +84,8 @@ def setUp(self): self.host = os.environ.get('AMQP_HOST', 'localhost') self.port = os.environ.get('AMQP_PORT', 5672) self.vhost = os.environ.get('AMQP_VHOST', self.VHOST + str(uuid.uuid4())) - self.http_client = pyrabbit.api.Client( - '{HOST}:15672/api/'.format(HOST=self.host), 'guest', 'guest', timeout=20 + self.http_client = pyrabbit2.api.Client( + '{HOST}:15672'.format(HOST=self.host), 'guest', 'guest', timeout=20 ) self.amqps = [] diff --git a/requirements_dev.txt b/requirements_dev.txt index 02eaa1e..46b56c8 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -5,4 +5,5 @@ coverage pylint Sphinx sphinx-rtd-theme --e git+https://github.com/bkjones/pyrabbit.git#egg=pyrabbit + +pyrabbit2 From 171eb9f5b3c92d4fe7f5fd897a78998bc708150a Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Thu, 29 Nov 2018 18:32:01 +0100 Subject: [PATCH 19/72] Use pamqp to encode value --- aioamqp/channel.py | 418 +++++++++++++----------------------- aioamqp/frame.py | 249 ++------------------- aioamqp/protocol.py | 85 ++++---- aioamqp/tests/test_basic.py | 2 +- aioamqp/tests/test_frame.py | 98 --------- 5 files changed, 204 insertions(+), 648 deletions(-) diff --git a/aioamqp/channel.py b/aioamqp/channel.py index bd8f2c7..5d25e95 100644 --- a/aioamqp/channel.py +++ b/aioamqp/channel.py @@ -9,6 +9,8 @@ from itertools import count import warnings +import pamqp.specification + from . import constants as amqp_constants from . import frame as amqp_frame from . import exceptions @@ -111,24 +113,24 @@ def dispatch_frame(self, frame): yield from methods[(frame.class_id, frame.method_id)](frame) @asyncio.coroutine - def _write_frame(self, frame, request, check_open=True, drain=True): + def _write_frame(self, channel_id, request, check_open=True, drain=True): yield from self.protocol.ensure_open() if not self.is_open and check_open: raise exceptions.ChannelClosed() - frame.write_frame(request) + amqp_frame.write(self.protocol._stream_writer, channel_id, request) if drain: yield from self.protocol._drain() @asyncio.coroutine - def _write_frame_awaiting_response(self, waiter_id, frame, request, no_wait, check_open=True, drain=True): + def _write_frame_awaiting_response(self, waiter_id, channel_id, request, no_wait, check_open=True, drain=True): '''Write a frame and set a waiter for the response (unless no_wait is set)''' if no_wait: - yield from self._write_frame(frame, request, check_open=check_open, drain=drain) + yield from self._write_frame(channel_id, request, check_open=check_open, drain=drain) return None f = self._set_waiter(waiter_id) try: - yield from self._write_frame(frame, request, check_open=check_open, drain=drain) + yield from self._write_frame(channel_id, request, check_open=check_open, drain=drain) except Exception: self._get_waiter(waiter_id) f.cancel() @@ -142,13 +144,9 @@ def _write_frame_awaiting_response(self, waiter_id, frame, request, no_wait, che @asyncio.coroutine def open(self): """Open the channel on the server.""" - frame = amqp_frame.AmqpRequest(self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_CHANNEL, amqp_constants.CHANNEL_OPEN) - request = amqp_frame.AmqpEncoder() - request.write_shortstr('') + request = pamqp.specification.Channel.Open() return (yield from self._write_frame_awaiting_response( - 'open', frame, request, no_wait=False, check_open=False)) + 'open', self.channel_id, request, no_wait=False, check_open=False)) @asyncio.coroutine def open_ok(self, frame): @@ -163,16 +161,9 @@ def close(self, reply_code=0, reply_text="Normal Shutdown"): if not self.is_open: raise exceptions.ChannelClosed("channel already closed or closing") self.close_event.set() - frame = amqp_frame.AmqpRequest(self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_CHANNEL, amqp_constants.CHANNEL_CLOSE) - request = amqp_frame.AmqpEncoder() - request.write_short(reply_code) - request.write_shortstr(reply_text) - request.write_short(0) - request.write_short(0) + request = pamqp.specification.Channel.Close(reply_code, reply_text, class_id=0, method_id=0) return (yield from self._write_frame_awaiting_response( - 'close', frame, request, no_wait=False, check_open=False)) + 'close', self.channel_id, request, no_wait=False, check_open=False)) @asyncio.coroutine def close_ok(self, frame): @@ -182,12 +173,8 @@ def close_ok(self, frame): @asyncio.coroutine def _send_channel_close_ok(self): - frame = amqp_frame.AmqpRequest( - self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_CHANNEL, amqp_constants.CHANNEL_CLOSE_OK) - request = amqp_frame.AmqpEncoder() - yield from self._write_frame(frame, request) + request = pamqp.specification.Channel.CloseOk() + yield from self._write_frame(self.channel_id, request) @asyncio.coroutine def server_channel_close(self, frame): @@ -202,13 +189,9 @@ def server_channel_close(self, frame): @asyncio.coroutine def flow(self, active): - frame = amqp_frame.AmqpRequest(self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_CHANNEL, amqp_constants.CHANNEL_FLOW) - request = amqp_frame.AmqpEncoder() - request.write_bits(active) + request = pamqp.specification.Channel.Flow(active) return (yield from self._write_frame_awaiting_response( - 'flow', frame, request, no_wait=False, + 'flow', self.channel_id, request, no_wait=False, check_open=False)) @asyncio.coroutine @@ -228,21 +211,18 @@ def flow_ok(self, frame): @asyncio.coroutine def exchange_declare(self, exchange_name, type_name, passive=False, durable=False, auto_delete=False, no_wait=False, arguments=None): - frame = amqp_frame.AmqpRequest(self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_EXCHANGE, amqp_constants.EXCHANGE_DECLARE) - request = amqp_frame.AmqpEncoder() - # short reserved-1 - request.write_short(0) - request.write_shortstr(exchange_name) - request.write_shortstr(type_name) - - internal = False # internal: deprecated - request.write_bits(passive, durable, auto_delete, internal, no_wait) - request.write_table(arguments) + request = pamqp.specification.Exchange.Declare( + exchange=exchange_name, + exchange_type=type_name, + passive=passive, + durable=durable, + auto_delete=auto_delete, + nowait=no_wait, + arguments=arguments + ) return (yield from self._write_frame_awaiting_response( - 'exchange_declare', frame, request, no_wait)) + 'exchange_declare', self.channel_id, request, no_wait)) @asyncio.coroutine def exchange_declare_ok(self, frame): @@ -253,17 +233,9 @@ def exchange_declare_ok(self, frame): @asyncio.coroutine def exchange_delete(self, exchange_name, if_unused=False, no_wait=False): - frame = amqp_frame.AmqpRequest(self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_EXCHANGE, amqp_constants.EXCHANGE_DELETE) - request = amqp_frame.AmqpEncoder() - # short reserved-1 - request.write_short(0) - request.write_shortstr(exchange_name) - request.write_bits(if_unused, no_wait) - + request = pamqp.specification.Exchange.Delete(exchange=exchange_name, if_unused=if_unused, nowait=no_wait) return (yield from self._write_frame_awaiting_response( - 'exchange_delete', frame, request, no_wait)) + 'exchange_delete', self.channel_id, request, no_wait)) @asyncio.coroutine def exchange_delete_ok(self, frame): @@ -276,20 +248,15 @@ def exchange_bind(self, exchange_destination, exchange_source, routing_key, no_wait=False, arguments=None): if arguments is None: arguments = {} - frame = amqp_frame.AmqpRequest(self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_EXCHANGE, amqp_constants.EXCHANGE_BIND) - - request = amqp_frame.AmqpEncoder() - request.write_short(0) # reserved - request.write_shortstr(exchange_destination) - request.write_shortstr(exchange_source) - request.write_shortstr(routing_key) - - request.write_bits(no_wait) - request.write_table(arguments) + request = pamqp.specification.Exchange.Bind( + destination=exchange_destination, + source=exchange_source, + routing_key=routing_key, + nowait=no_wait, + arguments=arguments + ) return (yield from self._write_frame_awaiting_response( - 'exchange_bind', frame, request, no_wait)) + 'exchange_bind', self.channel_id, request, no_wait)) @asyncio.coroutine def exchange_bind_ok(self, frame): @@ -302,20 +269,16 @@ def exchange_unbind(self, exchange_destination, exchange_source, routing_key, no_wait=False, arguments=None): if arguments is None: arguments = {} - frame = amqp_frame.AmqpRequest(self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.EXCHANGE_UNBIND, amqp_constants.EXCHANGE_UNBIND) - - request = amqp_frame.AmqpEncoder() - request.write_short(0) # reserved - request.write_shortstr(exchange_destination) - request.write_shortstr(exchange_source) - request.write_shortstr(routing_key) - - request.write_bits(no_wait) - request.write_table(arguments) + + request = pamqp.specification.Exchange.Unbind( + destination=exchange_destination, + source=exchange_source, + routing_key=routing_key, + nowait=no_wait, + arguments=arguments, + ) return (yield from self._write_frame_awaiting_response( - 'exchange_unbind', frame, request, no_wait)) + 'exchange_unbind', self.channel_id, request, no_wait)) @asyncio.coroutine def exchange_unbind_ok(self, frame): @@ -351,16 +314,17 @@ def queue_declare(self, queue_name=None, passive=False, durable=False, if not queue_name: queue_name = '' - frame = amqp_frame.AmqpRequest(self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_QUEUE, amqp_constants.QUEUE_DECLARE) - request = amqp_frame.AmqpEncoder() - request.write_short(0) # reserved - request.write_shortstr(queue_name) - request.write_bits(passive, durable, exclusive, auto_delete, no_wait) - request.write_table(arguments) + request = pamqp.specification.Queue.Declare( + queue=queue_name, + passive=passive, + durable=durable, + exclusive=exclusive, + auto_delete=auto_delete, + nowait=no_wait, + arguments=arguments + ) return (yield from self._write_frame_awaiting_response( - 'queue_declare', frame, request, no_wait)) + 'queue_declare', self.channel_id, request, no_wait)) @asyncio.coroutine def queue_declare_ok(self, frame): @@ -383,16 +347,14 @@ def queue_delete(self, queue_name, if_unused=False, if_empty=False, no_wait=Fals if_empty: bool, the queue is deleted if it has no messages. Raise if not. no_wait: bool, if set, the server will not respond to the method """ - frame = amqp_frame.AmqpRequest(self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_QUEUE, amqp_constants.QUEUE_DELETE) - - request = amqp_frame.AmqpEncoder() - request.write_short(0) # reserved - request.write_shortstr(queue_name) - request.write_bits(if_unused, if_empty, no_wait) + request = pamqp.specification.Queue.Delete( + queue=queue_name, + if_unused=if_unused, + if_empty=if_empty, + nowait=no_wait + ) return (yield from self._write_frame_awaiting_response( - 'queue_delete', frame, request, no_wait)) + 'queue_delete', self.channel_id, request, no_wait)) @asyncio.coroutine def queue_delete_ok(self, frame): @@ -405,20 +367,17 @@ def queue_bind(self, queue_name, exchange_name, routing_key, no_wait=False, argu """Bind a queue and a channel.""" if arguments is None: arguments = {} - frame = amqp_frame.AmqpRequest(self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_QUEUE, amqp_constants.QUEUE_BIND) - request = amqp_frame.AmqpEncoder() + request = pamqp.specification.Queue.Bind( + queue=queue_name, + exchange=exchange_name, + routing_key=routing_key, + nowait=no_wait, + arguments=arguments + ) # short reserved-1 - request.write_short(0) - request.write_shortstr(queue_name) - request.write_shortstr(exchange_name) - request.write_shortstr(routing_key) - request.write_octet(int(no_wait)) - request.write_table(arguments) return (yield from self._write_frame_awaiting_response( - 'queue_bind', frame, request, no_wait)) + 'queue_bind', self.channel_id, request, no_wait)) @asyncio.coroutine def queue_bind_ok(self, frame): @@ -430,19 +389,16 @@ def queue_bind_ok(self, frame): def queue_unbind(self, queue_name, exchange_name, routing_key, arguments=None): if arguments is None: arguments = {} - frame = amqp_frame.AmqpRequest(self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_QUEUE, amqp_constants.QUEUE_UNBIND) - request = amqp_frame.AmqpEncoder() - # short reserved-1 - request.write_short(0) - request.write_shortstr(queue_name) - request.write_shortstr(exchange_name) - request.write_shortstr(routing_key) - request.write_table(arguments) + request = pamqp.specification.Queue.Unbind( + queue=queue_name, + exchange=exchange_name, + routing_key=routing_key, + arguments=arguments + ) + return (yield from self._write_frame_awaiting_response( - 'queue_unbind', frame, request, no_wait=False)) + 'queue_unbind', self.channel_id, request, no_wait=False)) @asyncio.coroutine def queue_unbind_ok(self, frame): @@ -452,17 +408,11 @@ def queue_unbind_ok(self, frame): @asyncio.coroutine def queue_purge(self, queue_name, no_wait=False): - frame = amqp_frame.AmqpRequest(self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_QUEUE, amqp_constants.QUEUE_PURGE) - - request = amqp_frame.AmqpEncoder() - # short reserved-1 - request.write_short(0) - request.write_shortstr(queue_name) - request.write_octet(int(no_wait)) + request = pamqp.specification.Queue.Purge( + queue=queue_name, nowait=no_wait + ) return (yield from self._write_frame_awaiting_response( - 'queue_purge', frame, request, no_wait=no_wait)) + 'queue_purge', self.channel_id, request, no_wait=no_wait)) @asyncio.coroutine def queue_purge_ok(self, frame): @@ -481,41 +431,35 @@ def basic_publish(self, payload, exchange_name, routing_key, properties=None, ma warnings.warn("Str payload support will be removed in next release", DeprecationWarning) payload = payload.encode() - method_frame = amqp_frame.AmqpRequest( - self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - method_frame.declare_method( - amqp_constants.CLASS_BASIC, amqp_constants.BASIC_PUBLISH) - method_request = amqp_frame.AmqpEncoder() - method_request.write_short(0) - method_request.write_shortstr(exchange_name) - method_request.write_shortstr(routing_key) - method_request.write_bits(mandatory, immediate) - yield from self._write_frame(method_frame, method_request, drain=False) - - header_frame = amqp_frame.AmqpRequest( - self.protocol._stream_writer, amqp_constants.TYPE_HEADER, self.channel_id) - header_frame.declare_class(amqp_constants.CLASS_BASIC) - header_frame.set_body_size(len(payload)) - encoder = amqp_frame.AmqpEncoder() - encoder.write_message_properties(properties) - yield from self._write_frame(header_frame, encoder, drain=False) + if properties is None: + properties = {} + + method_request = pamqp.specification.Basic.Publish( + exchange=exchange_name, + routing_key=routing_key, + mandatory=mandatory, + immediate=immediate + ) + + yield from self._write_frame(self.channel_id, method_request, drain=False) + + header_request = pamqp.header.ContentHeader( + body_size=len(payload), + properties=pamqp.specification.Basic.Properties(**properties) + ) + yield from self._write_frame(self.channel_id, header_request, drain=False) # split the payload frame_max = self.protocol.server_frame_max or len(payload) for chunk in (payload[0+i:frame_max+i] for i in range(0, len(payload), frame_max)): - - content_frame = amqp_frame.AmqpRequest( - self.protocol._stream_writer, amqp_constants.TYPE_BODY, self.channel_id) - content_frame.declare_class(amqp_constants.CLASS_BASIC) - encoder = amqp_frame.AmqpEncoder() - encoder.payload.write(chunk) - yield from self._write_frame(content_frame, encoder, drain=False) + content_request = pamqp.body.ContentBody(chunk) + yield from self._write_frame(self.channel_id, content_request, drain=False) yield from self.protocol._drain() @asyncio.coroutine - def basic_qos(self, prefetch_size=0, prefetch_count=0, connection_global=None): + def basic_qos(self, prefetch_size=0, prefetch_count=0, connection_global=False): """Specifies quality of service. Args: @@ -531,17 +475,12 @@ def basic_qos(self, prefetch_size=0, prefetch_count=0, connection_global=None): settings should apply per-consumer channel; and global=true to mean that the QoS settings should apply per-channel. """ - frame = amqp_frame.AmqpRequest( - self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_BASIC, amqp_constants.BASIC_QOS) - request = amqp_frame.AmqpEncoder() - request.write_long(prefetch_size) - request.write_short(prefetch_count) - request.write_bits(connection_global) - + request = pamqp.specification.Basic.Qos( + prefetch_size, prefetch_count, connection_global + ) return (yield from self._write_frame_awaiting_response( - 'basic_qos', frame, request, no_wait=False)) + 'basic_qos', self.channel_id, request, no_wait=False) + ) @asyncio.coroutine def basic_qos_ok(self, frame): @@ -584,22 +523,21 @@ def basic_consume(self, callback, queue_name='', consumer_tag='', no_local=False if arguments is None: arguments = {} - frame = amqp_frame.AmqpRequest( - self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_BASIC, amqp_constants.BASIC_CONSUME) - request = amqp_frame.AmqpEncoder() - request.write_short(0) - request.write_shortstr(queue_name) - request.write_shortstr(consumer_tag) - request.write_bits(no_local, no_ack, exclusive, no_wait) - request.write_table(arguments) + request = pamqp.specification.Basic.Consume( + queue=queue_name, + consumer_tag=consumer_tag, + no_local=no_local, + no_ack=no_ack, + exclusive=exclusive, + nowait=no_wait, + arguments=arguments + ) self.consumer_callbacks[consumer_tag] = callback self.last_consumer_tag = consumer_tag return_value = yield from self._write_frame_awaiting_response( - 'basic_consume', frame, request, no_wait) + 'basic_consume', self.channel_id, request, no_wait) if no_wait: return_value = {'consumer_tag': consumer_tag} else: @@ -660,15 +598,10 @@ def server_basic_cancel(self, frame): @asyncio.coroutine def basic_cancel(self, consumer_tag, no_wait=False): - frame = amqp_frame.AmqpRequest( - self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_BASIC, amqp_constants.BASIC_CANCEL) - request = amqp_frame.AmqpEncoder() - request.write_shortstr(consumer_tag) - request.write_bits(no_wait) + request = pamqp.specification.Basic.Cancel(consumer_tag, no_wait) return (yield from self._write_frame_awaiting_response( - 'basic_cancel', frame, request, no_wait=no_wait)) + 'basic_cancel', self.channel_id, request, no_wait=no_wait) + ) @asyncio.coroutine def basic_cancel_ok(self, frame): @@ -681,16 +614,10 @@ def basic_cancel_ok(self, frame): @asyncio.coroutine def basic_get(self, queue_name='', no_ack=False): - frame = amqp_frame.AmqpRequest( - self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_BASIC, amqp_constants.BASIC_GET) - request = amqp_frame.AmqpEncoder() - request.write_short(0) - request.write_shortstr(queue_name) - request.write_bits(no_ack) + request = pamqp.specification.Basic.Get(queue=queue_name, no_ack=no_ack) return (yield from self._write_frame_awaiting_response( - 'basic_get', frame, request, no_wait=False)) + 'basic_get', self.channel_id, request, no_wait=False) + ) @asyncio.coroutine def basic_get_ok(self, frame): @@ -720,25 +647,13 @@ def basic_get_empty(self, frame): @asyncio.coroutine def basic_client_ack(self, delivery_tag, multiple=False): - frame = amqp_frame.AmqpRequest( - self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_BASIC, amqp_constants.BASIC_ACK) - request = amqp_frame.AmqpEncoder() - request.write_long_long(delivery_tag) - request.write_bits(multiple) - yield from self._write_frame(frame, request) + request = pamqp.specification.Basic.Ack(delivery_tag, multiple) + yield from self._write_frame(self.channel_id, request) @asyncio.coroutine def basic_client_nack(self, delivery_tag, multiple=False, requeue=True): - frame = amqp_frame.AmqpRequest( - self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_BASIC, amqp_constants.BASIC_NACK) - request = amqp_frame.AmqpEncoder() - request.write_long_long(delivery_tag) - request.write_bits(multiple, requeue) - yield from self._write_frame(frame, request) + request = pamqp.specification.Basic.Nack(delivery_tag, multiple, requeue) + yield from self._write_frame(self.channel_id, request) @asyncio.coroutine @@ -751,35 +666,20 @@ def basic_server_ack(self, frame): @asyncio.coroutine def basic_reject(self, delivery_tag, requeue=False): - frame = amqp_frame.AmqpRequest( - self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_BASIC, amqp_constants.BASIC_REJECT) - request = amqp_frame.AmqpEncoder() - request.write_long_long(delivery_tag) - request.write_bits(requeue) - yield from self._write_frame(frame, request) + request = pamqp.specification.Basic.Reject(delivery_tag, requeue) + yield from self._write_frame(self.channel_id, request) @asyncio.coroutine def basic_recover_async(self, requeue=True): - frame = amqp_frame.AmqpRequest( - self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_BASIC, amqp_constants.BASIC_RECOVER_ASYNC) - request = amqp_frame.AmqpEncoder() - request.write_bits(requeue) - yield from self._write_frame(frame, request) + request = pamqp.specification.Basic.RecoverAsync(requeue) + yield from self._write_frame(self.channel_id, request) @asyncio.coroutine def basic_recover(self, requeue=True): - frame = amqp_frame.AmqpRequest( - self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method( - amqp_constants.CLASS_BASIC, amqp_constants.BASIC_RECOVER) - request = amqp_frame.AmqpEncoder() - request.write_bits(requeue) + request = pamqp.specification.Basic.Recover(requeue) return (yield from self._write_frame_awaiting_response( - 'basic_recover', frame, request, no_wait=False)) + 'basic_recover', self.channel_id, request, no_wait=False) + ) @asyncio.coroutine def basic_recover_ok(self, frame): @@ -826,40 +726,33 @@ def publish(self, payload, exchange_name, routing_key, properties=None, mandator warnings.warn("Str payload support will be removed in next release", DeprecationWarning) payload = payload.encode() + if properties is None: + properties = {} + if self.publisher_confirms: delivery_tag = next(self.delivery_tag_iter) # pylint: disable=stop-iteration-return fut = self._set_waiter('basic_server_ack_{}'.format(delivery_tag)) - method_frame = amqp_frame.AmqpRequest( - self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - method_frame.declare_method( - amqp_constants.CLASS_BASIC, amqp_constants.BASIC_PUBLISH) - method_request = amqp_frame.AmqpEncoder() - method_request.write_short(0) - method_request.write_shortstr(exchange_name) - method_request.write_shortstr(routing_key) - method_request.write_bits(mandatory, immediate) - yield from self._write_frame(method_frame, method_request, drain=False) - - header_frame = amqp_frame.AmqpRequest( - self.protocol._stream_writer, amqp_constants.TYPE_HEADER, self.channel_id) - header_frame.declare_class(amqp_constants.CLASS_BASIC) - header_frame.set_body_size(len(payload)) - encoder = amqp_frame.AmqpEncoder() - encoder.write_message_properties(properties) - yield from self._write_frame(header_frame, encoder, drain=False) + method_request = pamqp.specification.Basic.Publish( + exchange=exchange_name, + routing_key=routing_key, + mandatory=mandatory, + immediate=immediate + ) + yield from self._write_frame(self.channel_id, method_request, drain=False) + + properties = pamqp.specification.Basic.Properties(**properties) + header_request = pamqp.header.ContentHeader( + body_size=len(payload), properties=properties + ) + yield from self._write_frame(self.channel_id, header_request, drain=False) # split the payload frame_max = self.protocol.server_frame_max or len(payload) for chunk in (payload[0+i:frame_max+i] for i in range(0, len(payload), frame_max)): - - content_frame = amqp_frame.AmqpRequest( - self.protocol._stream_writer, amqp_constants.TYPE_BODY, self.channel_id) - content_frame.declare_class(amqp_constants.CLASS_BASIC) - encoder = amqp_frame.AmqpEncoder() - encoder.payload.write(chunk) - yield from self._write_frame(content_frame, encoder, drain=False) + content_request = pamqp.body.ContentBody(chunk) + yield from self._write_frame(self.channel_id, content_request, drain=False) yield from self.protocol._drain() @@ -870,14 +763,11 @@ def publish(self, payload, exchange_name, routing_key, properties=None, mandator def confirm_select(self, *, no_wait=False): if self.publisher_confirms: raise ValueError('publisher confirms already enabled') - frame = amqp_frame.AmqpRequest( - self.protocol._stream_writer, amqp_constants.TYPE_METHOD, self.channel_id) - frame.declare_method(amqp_constants.CLASS_CONFIRM, amqp_constants.CONFIRM_SELECT) - request = amqp_frame.AmqpEncoder() - request.write_shortstr('') + request = pamqp.specification.Confirm.Select(nowait=no_wait) return (yield from self._write_frame_awaiting_response( - 'confirm_select', frame, request, no_wait)) + 'confirm_select', self.channel_id, request, no_wait) + ) @asyncio.coroutine def confirm_select_ok(self, frame): diff --git a/aioamqp/frame.py b/aioamqp/frame.py index 7b76c8f..e69cacb 100644 --- a/aioamqp/frame.py +++ b/aioamqp/frame.py @@ -47,6 +47,10 @@ from itertools import count from decimal import Decimal +import pamqp.encode +import pamqp.specification +import pamqp.frame + from . import exceptions from . import constants as amqp_constants from .properties import Properties @@ -55,200 +59,6 @@ DUMP_FRAMES = False -class AmqpEncoder: - - def __init__(self): - self.payload = io.BytesIO() - - def write_table(self, data_dict): - - self.write_long(0) # the table length (set later) - if data_dict: - start = self.payload.tell() - for key, value in data_dict.items(): - self.write_shortstr(key) - self.write_value(value) - table_length = self.payload.tell() - start - self.payload.seek(start - 4) # move before the long - self.write_long(table_length) # and set the table length - self.payload.seek(0, os.SEEK_END) # return at the end - - def write_array(self, value): - array_data = AmqpEncoder() - for item in value: - array_data.write_value(item) - array_data = array_data.payload.getvalue() - self.write_long(len(array_data)) - self.payload.write(array_data) - - def write_value(self, value): - if isinstance(value, (bytes, str)): - self.payload.write(b'S') - self.write_longstr(value) - elif isinstance(value, bool): - self.payload.write(b't') - self.write_bool(value) - elif isinstance(value, dict): - self.payload.write(b'F') - self.write_table(value) - elif isinstance(value, int): - if value.bit_length() >= 32: - self.payload.write(b'L') - self.write_long_long(value) - else: - self.payload.write(b'I') - self.write_long(value) - elif isinstance(value, float): - self.payload.write(b'd') - self.write_float(value) - elif isinstance(value, (list, tuple)): - self.payload.write(b'A') - self.write_array(value) - elif isinstance(value, Decimal): - self.payload.write(b'D') - self.write_decimal(value) - elif isinstance(value, datetime.datetime): - self.payload.write(b'T') - self.write_timestamp(value) - elif value is None: - self.payload.write(b'V') - else: - raise Exception("type({}) unsupported".format(type(value))) - - def write_bits(self, *args): - """Write consecutive bools to one byte""" - assert len(args) <= 8, "write_bits can only write 8 bits into one octet, sadly" - byte_value = 0 - - for arg_index, bit in enumerate(args): - if bit: - byte_value |= (1 << arg_index) - - self.write_octet(byte_value) - - def write_bool(self, value): - self.payload.write(struct.pack('?', value)) - - def write_octet(self, octet): - self.payload.write(struct.pack('!B', octet)) - - def write_short(self, short): - self.payload.write(struct.pack('!H', short)) - - def write_long(self, integer): - self.payload.write(struct.pack('!I', integer)) - - def write_long_long(self, longlong): - self.payload.write(struct.pack('!Q', longlong)) - - def write_float(self, value): - self.payload.write(struct.pack('>d', value)) - - def write_decimal(self, value): - sign, digits, exponent = value.as_tuple() - v = 0 - for d in digits: - v = (v * 10) + d - if sign: - v = -v - self.write_octet(-exponent) - self.payload.write(struct.pack('>i', v)) - - def write_timestamp(self, value): - """Write out a Python datetime.datetime object as a 64-bit integer representing seconds since the Unix epoch.""" - self.payload.write(struct.pack('>Q', int(value.replace(tzinfo=datetime.timezone.utc).timestamp()))) - - def _write_string(self, string): - if isinstance(string, str): - self.payload.write(string.encode()) - elif isinstance(string, bytes): - self.payload.write(string) - - def write_longstr(self, string): - self.write_long(len(string)) - self._write_string(string) - - def write_shortstr(self, string): - self.write_octet(len(string)) - self._write_string(string) - - def write_message_properties(self, properties): - - properties_flag_value = 0 - if properties is None: - self.write_short(0) - return - - diff = set(properties.keys()) - set(amqp_constants.MESSAGE_PROPERTIES) - if diff: - raise ValueError("%s are not properties, valid properties are %s" % ( - diff, amqp_constants.MESSAGE_PROPERTIES)) - - start = self.payload.tell() # record the position - self.write_short(properties_flag_value) # set the flag later - - content_type = properties.get('content_type') - if content_type: - properties_flag_value |= amqp_constants.FLAG_CONTENT_TYPE - self.write_shortstr(content_type) - content_encoding = properties.get('content_encoding') - if content_encoding: - properties_flag_value |= amqp_constants.FLAG_CONTENT_ENCODING - self.write_shortstr(content_encoding) - headers = properties.get('headers') - if headers is not None: - properties_flag_value |= amqp_constants.FLAG_HEADERS - self.write_table(headers) - delivery_mode = properties.get('delivery_mode') - if delivery_mode is not None: - properties_flag_value |= amqp_constants.FLAG_DELIVERY_MODE - self.write_octet(delivery_mode) - priority = properties.get('priority') - if priority is not None: - properties_flag_value |= amqp_constants.FLAG_PRIORITY - self.write_octet(priority) - correlation_id = properties.get('correlation_id') - if correlation_id: - properties_flag_value |= amqp_constants.FLAG_CORRELATION_ID - self.write_shortstr(correlation_id) - reply_to = properties.get('reply_to') - if reply_to: - properties_flag_value |= amqp_constants.FLAG_REPLY_TO - self.write_shortstr(reply_to) - expiration = properties.get('expiration') - if expiration: - properties_flag_value |= amqp_constants.FLAG_EXPIRATION - self.write_shortstr(expiration) - message_id = properties.get('message_id') - if message_id: - properties_flag_value |= amqp_constants.FLAG_MESSAGE_ID - self.write_shortstr(message_id) - timestamp = properties.get('timestamp') - if timestamp is not None: - properties_flag_value |= amqp_constants.FLAG_TIMESTAMP - self.write_long_long(timestamp) - type_ = properties.get('type') - if type_: - properties_flag_value |= amqp_constants.FLAG_TYPE - self.write_shortstr(type_) - user_id = properties.get('user_id') - if user_id: - properties_flag_value |= amqp_constants.FLAG_USER_ID - self.write_shortstr(user_id) - app_id = properties.get('app_id') - if app_id: - properties_flag_value |= amqp_constants.FLAG_APP_ID - self.write_shortstr(app_id) - cluster_id = properties.get('cluster_id') - if cluster_id: - properties_flag_value |= amqp_constants.FLAG_CLUSTER_ID - self.write_shortstr(cluster_id) - - self.payload.seek(start) # move before the flag - self.write_short(properties_flag_value) # set the flag - self.payload.seek(0, os.SEEK_END) - - class AmqpDecoder: def __init__(self, reader): self.reader = reader @@ -371,51 +181,16 @@ def read_field_array(self): return field_array -class AmqpRequest: - def __init__(self, writer, frame_type, channel): - self.writer = writer - self.frame_type = frame_type - self.channel = channel - self.class_id = None - self.weight = None - self.method_id = None - self.next_body_size = None - - def declare_class(self, class_id, weight=0): - self.class_id = class_id - self.weight = 0 - - def set_body_size(self, size): - self.next_body_size = size +def write(writer, channel, encoder): + """Writes the built frame from the encoder - def declare_method(self, class_id, method_id): - self.class_id = class_id - self.method_id = method_id + writer: asyncio StreamWriter + channel: amqp Channel identifier + encoder: frame encoder from pamqp which can be marshalled - def write_frame(self, encoder): - payload = encoder.payload - content_header = '' - transmission = io.BytesIO() - if self.frame_type == amqp_constants.TYPE_METHOD: - content_header = struct.pack('!HH', self.class_id, self.method_id) - elif self.frame_type == amqp_constants.TYPE_HEADER: - content_header = struct.pack('!HHQ', self.class_id, self.weight, self.next_body_size) - elif self.frame_type == amqp_constants.TYPE_BODY: - # no specific headers - pass - elif self.frame_type == amqp_constants.TYPE_HEARTBEAT: - # no specific headers - pass - else: - raise Exception("frame_type {} not handled".format(self.frame_type)) - - header = struct.pack('!BHI', self.frame_type, self.channel, payload.tell() + len(content_header)) - transmission.write(header) - if content_header: - transmission.write(content_header) - transmission.write(payload.getvalue()) - transmission.write(amqp_constants.FRAME_END) - return self.writer.write(transmission.getvalue()) + Returns int, the number of bytes written. + """ + return writer.write(pamqp.frame.marshal(encoder, channel)) class AmqpResponse: diff --git a/aioamqp/protocol.py b/aioamqp/protocol.py index 89c2f26..5beaa46 100644 --- a/aioamqp/protocol.py +++ b/aioamqp/protocol.py @@ -5,6 +5,10 @@ import asyncio import logging +import pamqp.frame +import pamqp.heartbeat +import pamqp.specification + from . import channel as amqp_channel from . import constants as amqp_constants from . import frame as amqp_frame @@ -150,8 +154,8 @@ def _drain(self): yield from self._stream_writer.drain() @asyncio.coroutine - def _write_frame(self, frame, request, drain=True): - frame.write_frame(request) + def _write_frame(self, channel_id, request, drain=True): + amqp_frame.write(self._stream_writer, channel_id, request) if drain: yield from self._drain() @@ -160,16 +164,14 @@ def close(self, no_wait=False, timeout=None): """Close connection (and all channels)""" yield from self.ensure_open() self.state = CLOSING - frame = amqp_frame.AmqpRequest(self._stream_writer, amqp_constants.TYPE_METHOD, 0) - frame.declare_method( - amqp_constants.CLASS_CONNECTION, amqp_constants.CONNECTION_CLOSE) - encoder = amqp_frame.AmqpEncoder() - # we request a clean connection close - encoder.write_short(0) - encoder.write_shortstr('') - encoder.write_short(0) - encoder.write_short(0) - yield from self._write_frame(frame, encoder) + request = pamqp.specification.Connection.Close( + reply_code=0, + reply_text='', + class_id=0, + method_id=0 + ) + + yield from self._write_frame(0, request) if not no_wait: yield from self.wait_closed(timeout=timeout) @@ -219,7 +221,7 @@ def start_connection(self, host, port, login, password, virtualhost, ssl=False, } # waiting reply start with credentions and co - yield from self.start_ok(client_properties, 'AMQPLAIN', auth, self.server_locales[0]) + yield from self.start_ok(client_properties, 'PLAIN', auth, self.server_locales[0]) # wait for a "tune" reponse yield from self.dispatch_frame() @@ -355,9 +357,8 @@ def send_heartbeat(self): It can be an ack for the server or the client willing to check for the connexion timeout """ - frame = amqp_frame.AmqpRequest(self._stream_writer, amqp_constants.TYPE_HEARTBEAT, 0) - request = amqp_frame.AmqpEncoder() - yield from self._write_frame(frame, request) + request = pamqp.heartbeat.Heartbeat() + yield from self._write_frame(0, request) def _heartbeat_timer_recv_timeout(self): # 4.2.7 If a peer detects no incoming traffic (i.e. received octets) for @@ -417,15 +418,16 @@ def start(self, frame): @asyncio.coroutine def start_ok(self, client_properties, mechanism, auth, locale): - frame = amqp_frame.AmqpRequest(self._stream_writer, amqp_constants.TYPE_METHOD, 0) - frame.declare_method( - amqp_constants.CLASS_CONNECTION, amqp_constants.CONNECTION_START_OK) - request = amqp_frame.AmqpEncoder() - request.write_table(client_properties) - request.write_shortstr(mechanism) - request.write_table(auth) - request.write_shortstr(locale.encode()) - yield from self._write_frame(frame, request) + def credentials(): + return '\0{LOGIN}\0{PASSWORD}'.format(**auth) + + request = pamqp.specification.Connection.StartOk( + client_properties=client_properties, + mechanism=mechanism, + locale=locale, + response=credentials() + ) + yield from self._write_frame(0, request) @asyncio.coroutine def server_close(self, frame): @@ -443,11 +445,8 @@ def server_close(self, frame): self._stream_writer.close() def _close_ok(self): - frame = amqp_frame.AmqpRequest(self._stream_writer, amqp_constants.TYPE_METHOD, 0) - frame.declare_method( - amqp_constants.CLASS_CONNECTION, amqp_constants.CONNECTION_CLOSE_OK) - request = amqp_frame.AmqpEncoder() - yield from self._write_frame(frame, request) + request = pamqp.specification.Connection.CloseOk() + yield from self._write_frame(0, request) @asyncio.coroutine def tune(self, frame): @@ -458,15 +457,10 @@ def tune(self, frame): @asyncio.coroutine def tune_ok(self, channel_max, frame_max, heartbeat): - frame = amqp_frame.AmqpRequest(self._stream_writer, amqp_constants.TYPE_METHOD, 0) - frame.declare_method( - amqp_constants.CLASS_CONNECTION, amqp_constants.CONNECTION_TUNE_OK) - encoder = amqp_frame.AmqpEncoder() - encoder.write_short(channel_max) - encoder.write_long(frame_max) - encoder.write_short(heartbeat) - - yield from self._write_frame(frame, encoder) + request = pamqp.specification.Connection.TuneOk( + channel_max, frame_max, heartbeat + ) + yield from self._write_frame(0, request) @asyncio.coroutine def secure_ok(self, login_response): @@ -475,15 +469,10 @@ def secure_ok(self, login_response): @asyncio.coroutine def open(self, virtual_host, capabilities='', insist=False): """Open connection to virtual host.""" - frame = amqp_frame.AmqpRequest(self._stream_writer, amqp_constants.TYPE_METHOD, 0) - frame.declare_method( - amqp_constants.CLASS_CONNECTION, amqp_constants.CONNECTION_OPEN) - encoder = amqp_frame.AmqpEncoder() - encoder.write_shortstr(virtual_host) - encoder.write_shortstr(capabilities) - encoder.write_bool(insist) - - yield from self._write_frame(frame, encoder) + request = pamqp.specification.Connection.Open( + virtual_host, capabilities, insist + ) + yield from self._write_frame(0, request) @asyncio.coroutine def open_ok(self, frame): diff --git a/aioamqp/tests/test_basic.py b/aioamqp/tests/test_basic.py index e34dfba..7e88a2f 100644 --- a/aioamqp/tests/test_basic.py +++ b/aioamqp/tests/test_basic.py @@ -40,7 +40,7 @@ def test_basic_qos_prefetch_size(self): @testing.coroutine def test_basic_qos_wrong_values(self): - with self.assertRaises(struct.error): + with self.assertRaises(TypeError): yield from self.channel.basic_qos( prefetch_size=100000, prefetch_count=1000000000, diff --git a/aioamqp/tests/test_frame.py b/aioamqp/tests/test_frame.py index 648d5e4..d5cfb5e 100644 --- a/aioamqp/tests/test_frame.py +++ b/aioamqp/tests/test_frame.py @@ -11,107 +11,9 @@ from .. import constants as amqp_constants from .. import frame as frame_module -from ..frame import AmqpEncoder from ..frame import AmqpResponse -class EncoderTestCase(unittest.TestCase): - """Test encoding of python builtin objects to AMQP frames.""" - - _multiprocess_can_split_ = True - - def setUp(self): - self.encoder = AmqpEncoder() - - def test_write_string(self): - self.encoder.write_value("foo") - self.assertEqual(self.encoder.payload.getvalue(), - # 'S' + size (4 bytes) + payload - b'S\x00\x00\x00\x03foo') - - def test_write_bool(self): - self.encoder.write_value(True) - self.assertEqual(self.encoder.payload.getvalue(), b't\x01') - - def test_write_array(self): - self.encoder.write_value(["v1", 123]) - self.assertEqual(self.encoder.payload.getvalue(), - # total size (4 bytes) + 'S' + size (4 bytes) + payload + 'I' + size (4 bytes) + payload - b'A\x00\x00\x00\x0cS\x00\x00\x00\x02v1I\x00\x00\x00{') - - def test_write_float(self): - self.encoder.write_value(1.1) - self.assertEqual(self.encoder.payload.getvalue(), b'd?\xf1\x99\x99\x99\x99\x99\x9a') - - def test_write_decimal(self): - self.encoder.write_value(Decimal("-1.1")) - self.assertEqual(self.encoder.payload.getvalue(), b'D\x01\xff\xff\xff\xf5') - - self.encoder.write_value(Decimal("1.1")) - self.assertEqual(self.encoder.payload.getvalue(), b'D\x01\xff\xff\xff\xf5D\x01\x00\x00\x00\x0b') - - def test_write_datetime(self): - self.encoder.write_value(datetime.datetime(2017, 12, 10, 4, 6, 49, 548918)) - self.assertEqual(self.encoder.payload.getvalue(), b'T\x00\x00\x00\x00Z,\xb2\xd9') - - def test_write_dict(self): - self.encoder.write_value({'foo': 'bar', 'bar': 'baz'}) - self.assertIn(self.encoder.payload.getvalue(), - # 'F' + total size + key (always a string) + value (with type) + ... - # The keys are not ordered, so the output is not deterministic (two possible values below) - (b'F\x00\x00\x00\x18\x03barS\x00\x00\x00\x03baz\x03fooS\x00\x00\x00\x03bar', - b'F\x00\x00\x00\x18\x03fooS\x00\x00\x00\x03bar\x03barS\x00\x00\x00\x03baz')) - - def test_write_none(self): - self.encoder.write_value(None) - self.assertEqual(self.encoder.payload.getvalue(), b'V') - - def test_write_message_properties_dont_crash(self): - properties = { - 'content_type': 'plain/text', - 'content_encoding': 'utf8', - 'headers': {'key': 'value'}, - 'delivery_mode': 2, - 'priority': 10, - 'correlation_id': '122', - 'reply_to': 'joe', - 'expiration': 'someday', - 'message_id': 'm_id', - 'timestamp': 12345, - 'type': 'a_type', - 'user_id': 'joe_42', - 'app_id': 'roxxor_app', - 'cluster_id': 'a_cluster', - } - self.encoder.write_message_properties(properties) - self.assertNotEqual(0, len(self.encoder.payload.getvalue())) - - def test_write_message_correlation_id_encode(self): - properties = { - 'delivery_mode': 2, - 'priority': 0, - 'correlation_id': '122', - } - self.encoder.write_message_properties(properties) - self.assertEqual(self.encoder.payload.getvalue(), b'\x1c\x00\x02\x00\x03122') - - def test_write_message_priority_zero(self): - properties = { - 'delivery_mode': 2, - 'priority': 0, - } - self.encoder.write_message_properties(properties) - self.assertEqual(self.encoder.payload.getvalue(), - b'\x18\x00\x02\x00') - - def test_write_message_properties_raises_on_invalid_property_name(self): - properties = { - 'invalid': 'coucou', - } - with self.assertRaises(ValueError): - self.encoder.write_message_properties(properties) - - class AmqpResponseTestCase(unittest.TestCase): def test_dump_dont_crash(self): frame = AmqpResponse(None) From 187f885de5b431f970724e1994e7595d460bb75a Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Fri, 30 Nov 2018 17:31:13 +0100 Subject: [PATCH 20/72] use pamqp to decode values --- aioamqp/channel.py | 164 ++++++++++----------- aioamqp/frame.py | 284 ++++-------------------------------- aioamqp/properties.py | 19 +++ aioamqp/protocol.py | 75 ++++------ aioamqp/tests/test_frame.py | 33 ----- aioamqp/tests/testcase.py | 2 +- aioamqp/tests/testing.py | 1 - 7 files changed, 163 insertions(+), 415 deletions(-) delete mode 100644 aioamqp/tests/test_frame.py diff --git a/aioamqp/channel.py b/aioamqp/channel.py index 5d25e95..0ae3249 100644 --- a/aioamqp/channel.py +++ b/aioamqp/channel.py @@ -14,6 +14,7 @@ from . import constants as amqp_constants from . import frame as amqp_frame from . import exceptions +from . import properties as amqp_properties from .envelope import Envelope, ReturnEnvelope @@ -77,40 +78,41 @@ def connection_closed(self, server_code=None, server_reason=None, exception=None @asyncio.coroutine def dispatch_frame(self, frame): methods = { - (amqp_constants.CLASS_CHANNEL, amqp_constants.CHANNEL_OPEN_OK): self.open_ok, - (amqp_constants.CLASS_CHANNEL, amqp_constants.CHANNEL_FLOW_OK): self.flow_ok, - (amqp_constants.CLASS_CHANNEL, amqp_constants.CHANNEL_CLOSE_OK): self.close_ok, - (amqp_constants.CLASS_CHANNEL, amqp_constants.CHANNEL_CLOSE): self.server_channel_close, - - (amqp_constants.CLASS_EXCHANGE, amqp_constants.EXCHANGE_DECLARE_OK): self.exchange_declare_ok, - (amqp_constants.CLASS_EXCHANGE, amqp_constants.EXCHANGE_BIND_OK): self.exchange_bind_ok, - (amqp_constants.CLASS_EXCHANGE, amqp_constants.EXCHANGE_UNBIND_OK): self.exchange_unbind_ok, - (amqp_constants.CLASS_EXCHANGE, amqp_constants.EXCHANGE_DELETE_OK): self.exchange_delete_ok, - - (amqp_constants.CLASS_QUEUE, amqp_constants.QUEUE_DECLARE_OK): self.queue_declare_ok, - (amqp_constants.CLASS_QUEUE, amqp_constants.QUEUE_DELETE_OK): self.queue_delete_ok, - (amqp_constants.CLASS_QUEUE, amqp_constants.QUEUE_BIND_OK): self.queue_bind_ok, - (amqp_constants.CLASS_QUEUE, amqp_constants.QUEUE_UNBIND_OK): self.queue_unbind_ok, - (amqp_constants.CLASS_QUEUE, amqp_constants.QUEUE_PURGE_OK): self.queue_purge_ok, - - (amqp_constants.CLASS_BASIC, amqp_constants.BASIC_QOS_OK): self.basic_qos_ok, - (amqp_constants.CLASS_BASIC, amqp_constants.BASIC_CONSUME_OK): self.basic_consume_ok, - (amqp_constants.CLASS_BASIC, amqp_constants.BASIC_CANCEL_OK): self.basic_cancel_ok, - (amqp_constants.CLASS_BASIC, amqp_constants.BASIC_GET_OK): self.basic_get_ok, - (amqp_constants.CLASS_BASIC, amqp_constants.BASIC_GET_EMPTY): self.basic_get_empty, - (amqp_constants.CLASS_BASIC, amqp_constants.BASIC_DELIVER): self.basic_deliver, - (amqp_constants.CLASS_BASIC, amqp_constants.BASIC_CANCEL): self.server_basic_cancel, - (amqp_constants.CLASS_BASIC, amqp_constants.BASIC_ACK): self.basic_server_ack, - (amqp_constants.CLASS_BASIC, amqp_constants.BASIC_NACK): self.basic_server_nack, - (amqp_constants.CLASS_BASIC, amqp_constants.BASIC_RECOVER_OK): self.basic_recover_ok, - (amqp_constants.CLASS_BASIC, amqp_constants.BASIC_RETURN): self.basic_return, - - (amqp_constants.CLASS_CONFIRM, amqp_constants.CONFIRM_SELECT_OK): self.confirm_select_ok, + pamqp.specification.Channel.OpenOk.name: self.open_ok, + pamqp.specification.Channel.FlowOk.name: self.flow_ok, + pamqp.specification.Channel.CloseOk.name: self.close_ok, + pamqp.specification.Channel.Close.name: self.server_channel_close, + + pamqp.specification.Exchange.DeclareOk.name: self.exchange_declare_ok, + pamqp.specification.Exchange.BindOk.name: self.exchange_bind_ok, + pamqp.specification.Exchange.UnbindOk.name: self.exchange_unbind_ok, + pamqp.specification.Exchange.DeleteOk.name: self.exchange_delete_ok, + + pamqp.specification.Queue.DeclareOk.name: self.queue_declare_ok, + pamqp.specification.Queue.DeleteOk.name: self.queue_delete_ok, + pamqp.specification.Queue.BindOk.name: self.queue_bind_ok, + pamqp.specification.Queue.UnbindOk.name: self.queue_unbind_ok, + pamqp.specification.Queue.PurgeOk.name: self.queue_purge_ok, + + pamqp.specification.Basic.QosOk.name: self.basic_qos_ok, + pamqp.specification.Basic.ConsumeOk.name: self.basic_consume_ok, + pamqp.specification.Basic.CancelOk.name: self.basic_cancel_ok, + pamqp.specification.Basic.GetOk.name: self.basic_get_ok, + pamqp.specification.Basic.GetEmpty.name: self.basic_get_empty, + pamqp.specification.Basic.Deliver.name: self.basic_deliver, + pamqp.specification.Basic.Cancel.name: self.server_basic_cancel, + pamqp.specification.Basic.Ack.name: self.basic_server_ack, + pamqp.specification.Basic.Nack.name: self.basic_server_nack, + pamqp.specification.Basic.RecoverOk.name: self.basic_recover_ok, + pamqp.specification.Basic.Return.name: self.basic_return, + + pamqp.specification.Confirm.SelectOk.name: self.confirm_select_ok, } - if (frame.class_id, frame.method_id) not in methods: - raise NotImplementedError("Frame (%s, %s) is not implemented" % (frame.class_id, frame.method_id)) - yield from methods[(frame.class_id, frame.method_id)](frame) + if frame.name not in methods: + raise NotImplementedError("Frame %s is not implemented" % frame.name) + + yield from methods[frame.name](frame) @asyncio.coroutine def _write_frame(self, channel_id, request, check_open=True, drain=True): @@ -180,10 +182,10 @@ def _send_channel_close_ok(self): def server_channel_close(self, frame): yield from self._send_channel_close_ok() results = { - 'reply_code': frame.payload_decoder.read_short(), - 'reply_text': frame.payload_decoder.read_shortstr(), - 'class_id': frame.payload_decoder.read_short(), - 'method_id': frame.payload_decoder.read_short(), + 'reply_code': frame.reply_code, + 'reply_text': frame.reply_text, + 'class_id': frame.class_id, + 'method_id': frame.method_id, } self.connection_closed(results['reply_code'], results['reply_text']) @@ -196,11 +198,9 @@ def flow(self, active): @asyncio.coroutine def flow_ok(self, frame): - decoder = amqp_frame.AmqpDecoder(frame.payload) - active = bool(decoder.read_octet()) self.close_event.clear() fut = self._get_waiter('flow') - fut.set_result({'active': active}) + fut.set_result({'active': frame.active}) logger.debug("Flow ok") @@ -329,9 +329,9 @@ def queue_declare(self, queue_name=None, passive=False, durable=False, @asyncio.coroutine def queue_declare_ok(self, frame): results = { - 'queue': frame.payload_decoder.read_shortstr(), - 'message_count': frame.payload_decoder.read_long(), - 'consumer_count': frame.payload_decoder.read_long(), + 'queue': frame.queue, + 'message_count': frame.message_count, + 'consumer_count': frame.consumer_count, } future = self._get_waiter('queue_declare') future.set_result(results) @@ -416,10 +416,8 @@ def queue_purge(self, queue_name, no_wait=False): @asyncio.coroutine def queue_purge_ok(self, frame): - decoder = amqp_frame.AmqpDecoder(frame.payload) - message_count = decoder.read_long() future = self._get_waiter('queue_purge') - future.set_result({'message_count': message_count}) + future.set_result({'message_count': frame.message_count}) # ## Basic class implementation @@ -492,8 +490,7 @@ def basic_qos_ok(self, frame): @asyncio.coroutine def basic_server_nack(self, frame, delivery_tag=None): if delivery_tag is None: - decoder = amqp_frame.AmqpDecoder(frame.payload) - delivery_tag = decoder.read_long_long() + delivery_tag = frame.delivery_tag fut = self._get_waiter('basic_server_ack_{}'.format(delivery_tag)) logger.debug('Received nack for delivery tag %r', delivery_tag) fut.set_exception(exceptions.PublishFailed(delivery_tag)) @@ -546,7 +543,7 @@ def basic_consume(self, callback, queue_name='', consumer_tag='', no_local=False @asyncio.coroutine def basic_consume_ok(self, frame): - ctag = frame.payload_decoder.read_shortstr() + ctag = frame.consumer_tag results = { 'consumer_tag': ctag, } @@ -556,22 +553,22 @@ def basic_consume_ok(self, frame): @asyncio.coroutine def basic_deliver(self, frame): - response = amqp_frame.AmqpDecoder(frame.payload) - consumer_tag = response.read_shortstr() - delivery_tag = response.read_long_long() - is_redeliver = response.read_bit() - exchange_name = response.read_shortstr() - routing_key = response.read_shortstr() - content_header_frame = yield from self.protocol.get_frame() + consumer_tag = frame.consumer_tag + delivery_tag = frame.delivery_tag + is_redeliver = frame.redelivered + exchange_name = frame.exchange + routing_key = frame.routing_key + channel, content_header_frame = yield from self.protocol.get_frame() buffer = io.BytesIO() + while(buffer.tell() < content_header_frame.body_size): - content_body_frame = yield from self.protocol.get_frame() - buffer.write(content_body_frame.payload) + _channel, content_body_frame = yield from self.protocol.get_frame() + buffer.write(content_body_frame.value) body = buffer.getvalue() envelope = Envelope(consumer_tag, delivery_tag, exchange_name, routing_key, is_redeliver) - properties = content_header_frame.properties + properties = amqp_properties.from_pamqp(content_header_frame.properties) callback = self.consumer_callbacks[consumer_tag] @@ -585,8 +582,8 @@ def basic_deliver(self, frame): @asyncio.coroutine def server_basic_cancel(self, frame): # https://www.rabbitmq.com/consumer-cancel.html - consumer_tag = frame.payload_decoder.read_shortstr() - _no_wait = frame.payload_decoder.read_bit() + consumer_tag = frame.consumer_tag + _no_wait = frame.nowait self.cancelled_consumers.add(consumer_tag) logger.info("consume cancelled received") for callback in self.cancellation_callbacks: @@ -606,7 +603,7 @@ def basic_cancel(self, consumer_tag, no_wait=False): @asyncio.coroutine def basic_cancel_ok(self, frame): results = { - 'consumer_tag': frame.payload_decoder.read_shortstr(), + 'consumer_tag': frame.consumer_tag, } future = self._get_waiter('basic_cancel') future.set_result(results) @@ -621,22 +618,23 @@ def basic_get(self, queue_name='', no_ack=False): @asyncio.coroutine def basic_get_ok(self, frame): - data = {} - decoder = amqp_frame.AmqpDecoder(frame.payload) - data['delivery_tag'] = decoder.read_long_long() - data['redelivered'] = bool(decoder.read_octet()) - data['exchange_name'] = decoder.read_shortstr() - data['routing_key'] = decoder.read_shortstr() - data['message_count'] = decoder.read_long() - content_header_frame = yield from self.protocol.get_frame() + data = { + 'delivery_tag': frame.delivery_tag, + 'redelivered': frame.redelivered, + 'exchange_name': frame.exchange, + 'routing_key': frame.routing_key, + 'message_count': frame.message_count, + } + + channel, content_header_frame = yield from self.protocol.get_frame() buffer = io.BytesIO() while(buffer.tell() < content_header_frame.body_size): - content_body_frame = yield from self.protocol.get_frame() - buffer.write(content_body_frame.payload) + _channel, content_body_frame = yield from self.protocol.get_frame() + buffer.write(content_body_frame.value) data['message'] = buffer.getvalue() - data['properties'] = content_header_frame.properties + data['properties'] = amqp_properties.from_pamqp(content_header_frame.properties) future = self._get_waiter('basic_get') future.set_result(data) @@ -658,8 +656,7 @@ def basic_client_nack(self, delivery_tag, multiple=False, requeue=True): @asyncio.coroutine def basic_server_ack(self, frame): - decoder = amqp_frame.AmqpDecoder(frame.payload) - delivery_tag = decoder.read_long_long() + delivery_tag = frame.delivery_tag fut = self._get_waiter('basic_server_ack_{}'.format(delivery_tag)) logger.debug('Received ack for delivery tag %s', delivery_tag) fut.set_result(True) @@ -689,22 +686,21 @@ def basic_recover_ok(self, frame): @asyncio.coroutine def basic_return(self, frame): - response = amqp_frame.AmqpDecoder(frame.payload) - reply_code = response.read_short() - reply_text = response.read_shortstr() - exchange_name = response.read_shortstr() - routing_key = response.read_shortstr() - content_header_frame = yield from self.protocol.get_frame() + reply_code = frame.reply_code + reply_text = frame.reply_text + exchange_name = frame.exchange + routing_key = frame.routing_key + channel, content_header_frame = yield from self.protocol.get_frame() buffer = io.BytesIO() - while buffer.tell() < content_header_frame.body_size: - content_body_frame = yield from self.protocol.get_frame() - buffer.write(content_body_frame.payload) + while(buffer.tell() < content_header_frame.body_size): + _channel, content_body_frame = yield from self.protocol.get_frame() + buffer.write(content_body_frame.value) body = buffer.getvalue() envelope = ReturnEnvelope(reply_code, reply_text, exchange_name, routing_key) - properties = content_header_frame.properties + properties = amqp_properties.from_pamqp(content_header_frame.properties) callback = self.return_callback if callback is None: # they have set mandatory bit, but havent added a callback diff --git a/aioamqp/frame.py b/aioamqp/frame.py index e69cacb..bd1a5d1 100644 --- a/aioamqp/frame.py +++ b/aioamqp/frame.py @@ -59,128 +59,6 @@ DUMP_FRAMES = False -class AmqpDecoder: - def __init__(self, reader): - self.reader = reader - - def read_bit(self): - return bool(self.read_octet()) - - def read_octet(self): - data = self.reader.read(1) - return ord(data) - - def read_signed_octet(self): - data = self.reader.read(1) - return struct.unpack('!b', data)[0] - - def read_short(self): - data = self.reader.read(2) - return struct.unpack('!H', data)[0] - - def read_signed_short(self): - data = self.reader.read(2) - return struct.unpack('!h', data)[0] - - def read_long(self): - data = self.reader.read(4) - return struct.unpack('!I', data)[0] - - def read_signed_long(self): - data = self.reader.read(4) - return struct.unpack('!i', data)[0] - - def read_long_long(self): - data = self.reader.read(8) - return struct.unpack('!Q', data)[0] - - def read_signed_long_long(self): - data = self.reader.read(8) - return struct.unpack('!q', data)[0] - - def read_float(self): - # XXX: This used to read & unpack '!d', which is a double, not a shorter float - data = self.reader.read(4) - return struct.unpack('!f', data)[0] - - def read_double(self): - data = self.reader.read(8) - return struct.unpack('!d', data)[0] - - def read_decimal(self): - decimals = self.read_octet() - value = self.read_signed_long() - return Decimal(value) * (Decimal(10) ** -decimals) - - def read_shortstr(self): - data = self.reader.read(1) - string_len = struct.unpack('!B', data)[0] - data = self.reader.read(string_len) - return data.decode() - - def read_longstr(self): - string_len = self.read_long() - data = self.reader.read(string_len) - return data.decode() - - def read_timestamp(self): - return datetime.datetime.fromtimestamp(self.read_long_long(), datetime.timezone.utc) - - def read_table(self): - """Reads an AMQP table""" - table_len = self.read_long() - table_data = AmqpDecoder(io.BytesIO(self.reader.read(table_len))) - table = {} - while table_data.reader.tell() < table_len: - var_name = table_data.read_shortstr() - var_value = self.read_table_subitem(table_data) - table[var_name] = var_value - return table - - _table_subitem_reader_map = { - 't': 'read_bit', - 'b': 'read_octet', - 'B': 'read_signed_octet', - 'U': 'read_signed_short', - 'u': 'read_short', - 'I': 'read_signed_long', - 'i': 'read_long', - 'L': 'read_unsigned_long_long', - 'l': 'read_long_long', - 'f': 'read_float', - 'd': 'read_float', - 'D': 'read_decimal', - 's': 'read_shortstr', - 'S': 'read_longstr', - 'A': 'read_field_array', - 'T': 'read_timestamp', - 'F': 'read_table', - } - - def read_table_subitem(self, table_data): - """Read `table_data` bytes, guess the type of the value, and cast it. - - table_data: a pair of b'' - """ - value_type = chr(table_data.read_octet()) - if value_type == 'V': - return None - else: - reader_name = self._table_subitem_reader_map.get(value_type) - if not reader_name: - raise ValueError('Unknown value_type {}'.format(value_type)) - return getattr(table_data, reader_name)() - - def read_field_array(self): - array_len = self.read_long() - array_data = AmqpDecoder(io.BytesIO(self.reader.read(array_len))) - field_array = [] - while array_data.reader.tell() < array_len: - item = self.read_table_subitem(array_data) - field_array.append(item) - return field_array - - def write(writer, channel, encoder): """Writes the built frame from the encoder @@ -193,137 +71,39 @@ def write(writer, channel, encoder): return writer.write(pamqp.frame.marshal(encoder, channel)) -class AmqpResponse: - """Read a response from the AMQP server +@asyncio.coroutine +def read(reader): + """Read a new frame from the wire + + reader: asyncio StreamReader + Returns (channel, frame) a tuple containing both channel and the pamqp frame, + the object describing the frame """ - def __init__(self, reader): - self.reader = reader - self.frame_type = None - self.channel = 0 # default channel in AMQP - self.payload_size = None - self.frame_end = None - self.frame_payload = None - self.payload = None - self.frame_class = None - self.frame_method = None - self.class_id = None - self.method_id = None - self.weight = None - self.body_size = None - self.property_flags = None - self.properties = None - self.arguments = {} - self.frame_length = 0 - - self.payload_decoder = None - self.header_decoder = None - - @asyncio.coroutine - def read_frame(self): - """Decode the frame""" - if not self.reader: - raise exceptions.AmqpClosedConnection() - try: - data = yield from self.reader.readexactly(7) - except (asyncio.IncompleteReadError, socket.error) as ex: - raise exceptions.AmqpClosedConnection() from ex - - frame_header = io.BytesIO(data) - self.header_decoder = AmqpDecoder(frame_header) - self.frame_type = self.header_decoder.read_octet() - self.channel = self.header_decoder.read_short() - self.frame_length = self.header_decoder.read_long() - payload_data = yield from self.reader.readexactly(self.frame_length) - - if self.frame_type == amqp_constants.TYPE_METHOD: - self.payload = io.BytesIO(payload_data) - self.payload_decoder = AmqpDecoder(self.payload) - self.class_id = self.payload_decoder.read_short() - self.method_id = self.payload_decoder.read_short() - - elif self.frame_type == amqp_constants.TYPE_HEADER: - self.payload = io.BytesIO(payload_data) - self.payload_decoder = AmqpDecoder(self.payload) - self.class_id = self.payload_decoder.read_short() - self.weight = self.payload_decoder.read_short() - self.body_size = self.payload_decoder.read_long_long() - self.property_flags = 0 - for flagword_index in count(0): - partial_flags = self.payload_decoder.read_short() - self.property_flags |= partial_flags << (flagword_index * 16) - if partial_flags & 1 == 0: - break - decoded_properties = {} - if self.property_flags & amqp_constants.FLAG_CONTENT_TYPE: - decoded_properties['content_type'] = self.payload_decoder.read_shortstr() - if self.property_flags & amqp_constants.FLAG_CONTENT_ENCODING: - decoded_properties['content_encoding'] = self.payload_decoder.read_shortstr() - if self.property_flags & amqp_constants.FLAG_HEADERS: - decoded_properties['headers'] = self.payload_decoder.read_table() - if self.property_flags & amqp_constants.FLAG_DELIVERY_MODE: - decoded_properties['delivery_mode'] = self.payload_decoder.read_octet() - if self.property_flags & amqp_constants.FLAG_PRIORITY: - decoded_properties['priority'] = self.payload_decoder.read_octet() - if self.property_flags & amqp_constants.FLAG_CORRELATION_ID: - decoded_properties['correlation_id'] = self.payload_decoder.read_shortstr() - if self.property_flags & amqp_constants.FLAG_REPLY_TO: - decoded_properties['reply_to'] = self.payload_decoder.read_shortstr() - if self.property_flags & amqp_constants.FLAG_EXPIRATION: - decoded_properties['expiration'] = self.payload_decoder.read_shortstr() - if self.property_flags & amqp_constants.FLAG_MESSAGE_ID: - decoded_properties['message_id'] = self.payload_decoder.read_shortstr() - if self.property_flags & amqp_constants.FLAG_TIMESTAMP: - decoded_properties['timestamp'] = self.payload_decoder.read_long_long() - if self.property_flags & amqp_constants.FLAG_TYPE: - decoded_properties['type'] = self.payload_decoder.read_shortstr() - if self.property_flags & amqp_constants.FLAG_USER_ID: - decoded_properties['user_id'] = self.payload_decoder.read_shortstr() - if self.property_flags & amqp_constants.FLAG_APP_ID: - decoded_properties['app_id'] = self.payload_decoder.read_shortstr() - if self.property_flags & amqp_constants.FLAG_CLUSTER_ID: - decoded_properties['cluster_id'] = self.payload_decoder.read_shortstr() - self.properties = Properties(**decoded_properties) - - elif self.frame_type == amqp_constants.TYPE_BODY: - self.payload = payload_data - - elif self.frame_type == amqp_constants.TYPE_HEARTBEAT: - pass - - else: - raise ValueError("Message type {:x} not known".format(self.frame_type)) - self.frame_end = yield from self.reader.readexactly(1) - assert self.frame_end == amqp_constants.FRAME_END - - def __str__(self): - frame_data = { - 'type': self.frame_type, - 'channel': self.channel, - 'size': self.payload_size, - 'frame_end': self.frame_end, - 'payload': self.frame_payload, - } - output = """ -0 1 3 7 size+7 size+8 -+--------+-----------+------------+ +---------------+ +--------------+ -|{type!r:^8}|{channel!r:^11}|{size!r:^12}| |{payload!r:^15}| |{frame_end!r:^14}| -+--------+-----------+------------+ +---------------+ +--------------+ - type channel size payload frame-end -""".format(**frame_data) - - if self.frame_type == amqp_constants.TYPE_METHOD: - method_data = { - 'class_id': self.class_id, - 'method_id': self.method_id, - } - type_output = """ -0 2 4 -+----------+-----------+-------------- - - -|{class_id:^10}|{method_id:^11}| arguments... -+----------+-----------+-------------- - - - class-id method-id ...""".format(**method_data) + if not reader: + raise exceptions.AmqpClosedConnection() + try: + data = yield from reader.readexactly(7) + except (asyncio.IncompleteReadError, socket.error) as ex: + raise exceptions.AmqpClosedConnection() from ex + + frame_type, channel, frame_length = pamqp.frame._frame_parts(data) + + payload_data = yield from reader.readexactly(frame_length) + frame = None + + if frame_type == amqp_constants.TYPE_METHOD: + frame = pamqp.frame._unmarshal_method_frame(payload_data) + + elif frame_type == amqp_constants.TYPE_HEADER: + frame = pamqp.frame._unmarshal_header_frame(payload_data) + + elif frame_type == amqp_constants.TYPE_BODY: + frame = pamqp.frame._unmarshal_body_frame(payload_data) - output += os.linesep + type_output + elif frame_type == amqp_constants.TYPE_HEARTBEAT: + frame = pamqp.heartbeat.Heartbeat() - return output + frame_end = yield from reader.readexactly(1) + assert frame_end == amqp_constants.FRAME_END + return channel, frame diff --git a/aioamqp/properties.py b/aioamqp/properties.py index 7cc3d1d..27abd3e 100644 --- a/aioamqp/properties.py +++ b/aioamqp/properties.py @@ -23,3 +23,22 @@ def __init__( self.user_id = user_id self.app_id = app_id self.cluster_id = cluster_id + + +def from_pamqp(instance): + props = Properties() + props.content_type = instance.content_type + props.content_encoding = instance.content_encoding + props.headers = instance.headers + props.delivery_mode = instance.delivery_mode + props.priority = instance.priority + props.correlation_id = instance.correlation_id + props.reply_to = instance.reply_to + props.expiration = instance.expiration + props.message_id = instance.message_id + props.timestamp = instance.timestamp + props.type = instance.message_type + props.user_id = instance.user_id + props.app_id = instance.app_id + props.cluster_id = instance.cluster_id + return props diff --git a/aioamqp/protocol.py b/aioamqp/protocol.py index 5beaa46..6cd74b0 100644 --- a/aioamqp/protocol.py +++ b/aioamqp/protocol.py @@ -186,7 +186,6 @@ def wait_closed(self, timeout=None): @asyncio.coroutine def close_ok(self, frame): - logger.info("Recv close ok") self._stream_writer.close() @asyncio.coroutine @@ -221,7 +220,7 @@ def start_connection(self, host, port, login, password, virtualhost, ssl=False, } # waiting reply start with credentions and co - yield from self.start_ok(client_properties, 'PLAIN', auth, self.server_locales[0]) + yield from self.start_ok(client_properties, 'PLAIN', auth, self.server_locales) # wait for a "tune" reponse yield from self.dispatch_frame() @@ -247,13 +246,8 @@ def start_connection(self, host, port, login, password, virtualhost, ssl=False, yield from self.open(virtualhost, capabilities='', insist=insist) # wait for open-ok - frame = yield from self.get_frame() - yield from self.dispatch_frame(frame) - if (frame.frame_type == amqp_constants.TYPE_METHOD and - frame.class_id == amqp_constants.CLASS_CONNECTION and - frame.method_id == amqp_constants.CONNECTION_CLOSE): - raise exceptions.AmqpClosedConnection() - + channel, frame = yield from self.get_frame() + yield from self.dispatch_frame(channel, frame) # for now, we read server's responses asynchronously self.worker = ensure_future(self.run(), loop=self._loop) @@ -262,40 +256,37 @@ def get_frame(self): """Read the frame, and only decode its header """ - frame = amqp_frame.AmqpResponse(self._stream_reader) - yield from frame.read_frame() - - return frame + return amqp_frame.read(self._stream_reader) @asyncio.coroutine - def dispatch_frame(self, frame=None): + def dispatch_frame(self, frame_channel=None, frame=None): """Dispatch the received frame to the corresponding handler""" method_dispatch = { - (amqp_constants.CLASS_CONNECTION, amqp_constants.CONNECTION_CLOSE): self.server_close, - (amqp_constants.CLASS_CONNECTION, amqp_constants.CONNECTION_CLOSE_OK): self.close_ok, - (amqp_constants.CLASS_CONNECTION, amqp_constants.CONNECTION_TUNE): self.tune, - (amqp_constants.CLASS_CONNECTION, amqp_constants.CONNECTION_START): self.start, - (amqp_constants.CLASS_CONNECTION, amqp_constants.CONNECTION_OPEN_OK): self.open_ok, + pamqp.specification.Connection.Close.name: self.server_close, + pamqp.specification.Connection.CloseOk.name: self.close_ok, + pamqp.specification.Connection.Tune.name: self.tune, + pamqp.specification.Connection.Start.name: self.start, + pamqp.specification.Connection.OpenOk.name: self.open_ok, } - if not frame: - frame = yield from self.get_frame() + if not frame_channel and not frame: + frame_channel, frame = yield from self.get_frame() - if frame.frame_type == amqp_constants.TYPE_HEARTBEAT: + if isinstance(frame, pamqp.heartbeat.Heartbeat): return - if frame.channel is not 0: - channel = self.channels.get(frame.channel) + if frame_channel is not 0: + channel = self.channels.get(frame_channel) if channel is not None: yield from channel.dispatch_frame(frame) else: - logger.info("Unknown channel %s", frame.channel) + logger.info("Unknown channel %s", frame_channel) return - if (frame.class_id, frame.method_id) not in method_dispatch: - logger.info("frame %s %s is not handled", frame.class_id, frame.method_id) + if frame.name not in method_dispatch: + logger.info("frame %s is not handled", frame.name) return - yield from method_dispatch[(frame.class_id, frame.method_id)](frame) + yield from method_dispatch[frame.name](frame) def release_channel_id(self, channel_id): """Called from the channel instance, it relase a previously used @@ -408,13 +399,11 @@ def _heartbeat(self): @asyncio.coroutine def start(self, frame): """Method sent from the server to begin a new connection""" - response = amqp_frame.AmqpDecoder(frame.payload) - - self.version_major = response.read_octet() - self.version_minor = response.read_octet() - self.server_properties = response.read_table() - self.server_mechanisms = response.read_longstr().split(' ') - self.server_locales = response.read_longstr().split(' ') + self.version_major = frame.version_major + self.version_minor = frame.version_minor + self.server_properties = frame.server_properties + self.server_mechanisms = frame.mechanisms + self.server_locales = frame.locales @asyncio.coroutine def start_ok(self, client_properties, mechanism, auth, locale): @@ -433,11 +422,10 @@ def credentials(): def server_close(self, frame): """The server is closing the connection""" self.state = CLOSING - response = amqp_frame.AmqpDecoder(frame.payload) - reply_code = response.read_short() - reply_text = response.read_shortstr() - class_id = response.read_short() - method_id = response.read_short() + reply_code = frame.reply_code + reply_text = frame.reply_text + class_id = frame.class_id + method_id = frame.method_id logger.warning("Server closed connection: %s, code=%s, class_id=%s, method_id=%s", reply_text, reply_code, class_id, method_id) self._close_channels(reply_code, reply_text) @@ -450,10 +438,9 @@ def _close_ok(self): @asyncio.coroutine def tune(self, frame): - decoder = amqp_frame.AmqpDecoder(frame.payload) - self.server_channel_max = decoder.read_short() - self.server_frame_max = decoder.read_long() - self.server_heartbeat = decoder.read_short() + self.server_channel_max = frame.channel_max + self.server_frame_max = frame.frame_max + self.server_heartbeat = frame.heartbeat @asyncio.coroutine def tune_ok(self, channel_max, frame_max, heartbeat): diff --git a/aioamqp/tests/test_frame.py b/aioamqp/tests/test_frame.py deleted file mode 100644 index d5cfb5e..0000000 --- a/aioamqp/tests/test_frame.py +++ /dev/null @@ -1,33 +0,0 @@ -""" - Test frame format. -""" - -import io -import unittest -import sys -import datetime - -from decimal import Decimal - -from .. import constants as amqp_constants -from .. import frame as frame_module -from ..frame import AmqpResponse - - -class AmqpResponseTestCase(unittest.TestCase): - def test_dump_dont_crash(self): - frame = AmqpResponse(None) - frame.frame_type = amqp_constants.TYPE_METHOD - frame.class_id = 0 - frame.method_id = 0 - saved_stout = sys.stdout - frame_module.DUMP_FRAMES = True - sys.stdout = io.StringIO() - try: - last_len = len(sys.stdout.getvalue()) - print(self) - # assert something has been writen - self.assertLess(last_len, len(sys.stdout.getvalue())) - finally: - frame_module.DUMP_FRAMES = False - sys.stdout = saved_stout diff --git a/aioamqp/tests/testcase.py b/aioamqp/tests/testcase.py index 3be37cf..20a0fe7 100644 --- a/aioamqp/tests/testcase.py +++ b/aioamqp/tests/testcase.py @@ -150,7 +150,7 @@ def server_version(self, amqp=None): if amqp is None: amqp = self.amqp - server_version = tuple(int(x) for x in amqp.server_properties['version'].split('.')) + server_version = tuple(int(x) for x in amqp.server_properties['version'].decode().split('.')) return server_version @asyncio.coroutine diff --git a/aioamqp/tests/testing.py b/aioamqp/tests/testing.py index 3d9c0b9..b0c5f87 100644 --- a/aioamqp/tests/testing.py +++ b/aioamqp/tests/testing.py @@ -16,7 +16,6 @@ def __init__(self): def emit(self, record): message = record.msg % record.args - print(message) self.messages.append(message) From 948e69d149e7429f2da781c203d13556024c22c0 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Mon, 3 Dec 2018 09:49:01 +0100 Subject: [PATCH 21/72] add pamqp to requirements in setup.py --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index b83b136..c7d5415 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,9 @@ packages=[ 'aioamqp', ], + install_requires=[ + 'pamqp>=2.0,<3', + ], extras_require={ ':python_version=="3.3"': ['asyncio'], }, From 4d20ba54411b70a05a6b159827f3c46e8bcb4be8 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Tue, 11 Dec 2018 15:09:51 +0100 Subject: [PATCH 22/72] Ensure connection is open before starting the worker --- aioamqp/protocol.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aioamqp/protocol.py b/aioamqp/protocol.py index 6cd74b0..c0d3c99 100644 --- a/aioamqp/protocol.py +++ b/aioamqp/protocol.py @@ -248,6 +248,8 @@ def start_connection(self, host, port, login, password, virtualhost, ssl=False, # wait for open-ok channel, frame = yield from self.get_frame() yield from self.dispatch_frame(channel, frame) + + yield from self.ensure_open() # for now, we read server's responses asynchronously self.worker = ensure_future(self.run(), loop=self._loop) From 4c176c5e957a210f49e1aedca7a291ccff97db00 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Tue, 11 Dec 2018 15:10:11 +0100 Subject: [PATCH 23/72] syntax --- aioamqp/protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aioamqp/protocol.py b/aioamqp/protocol.py index c0d3c99..deb79d0 100644 --- a/aioamqp/protocol.py +++ b/aioamqp/protocol.py @@ -271,7 +271,7 @@ def dispatch_frame(self, frame_channel=None, frame=None): pamqp.specification.Connection.Start.name: self.start, pamqp.specification.Connection.OpenOk.name: self.open_ok, } - if not frame_channel and not frame: + if frame_channel is None and frame is None: frame_channel, frame = yield from self.get_frame() if isinstance(frame, pamqp.heartbeat.Heartbeat): From d5d7ea9e839860b34db36e93225c869ce469c255 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Thu, 27 Dec 2018 14:52:07 +0100 Subject: [PATCH 24/72] few pylints --- aioamqp/channel.py | 7 +++---- aioamqp/frame.py | 7 ------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/aioamqp/channel.py b/aioamqp/channel.py index 0ae3249..aeb42f0 100644 --- a/aioamqp/channel.py +++ b/aioamqp/channel.py @@ -11,7 +11,6 @@ import pamqp.specification -from . import constants as amqp_constants from . import frame as amqp_frame from . import exceptions from . import properties as amqp_properties @@ -558,7 +557,7 @@ def basic_deliver(self, frame): is_redeliver = frame.redelivered exchange_name = frame.exchange routing_key = frame.routing_key - channel, content_header_frame = yield from self.protocol.get_frame() + _channel, content_header_frame = yield from self.protocol.get_frame() buffer = io.BytesIO() @@ -626,7 +625,7 @@ def basic_get_ok(self, frame): 'message_count': frame.message_count, } - channel, content_header_frame = yield from self.protocol.get_frame() + _channel, content_header_frame = yield from self.protocol.get_frame() buffer = io.BytesIO() while(buffer.tell() < content_header_frame.body_size): @@ -690,7 +689,7 @@ def basic_return(self, frame): reply_text = frame.reply_text exchange_name = frame.exchange routing_key = frame.routing_key - channel, content_header_frame = yield from self.protocol.get_frame() + _channel, content_header_frame = yield from self.protocol.get_frame() buffer = io.BytesIO() while(buffer.tell() < content_header_frame.body_size): diff --git a/aioamqp/frame.py b/aioamqp/frame.py index bd1a5d1..c3ce218 100644 --- a/aioamqp/frame.py +++ b/aioamqp/frame.py @@ -39,13 +39,7 @@ """ import asyncio -import io -import struct import socket -import os -import datetime -from itertools import count -from decimal import Decimal import pamqp.encode import pamqp.specification @@ -53,7 +47,6 @@ from . import exceptions from . import constants as amqp_constants -from .properties import Properties DUMP_FRAMES = False From 5d90ec6d4f9fae901913cd36c3ecf6d2dadd7e29 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Thu, 3 Jan 2019 10:15:45 +0100 Subject: [PATCH 25/72] Login method is now 'PLAIN' instead of AMQPLAIN --- aioamqp/protocol.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aioamqp/protocol.py b/aioamqp/protocol.py index deb79d0..1433393 100644 --- a/aioamqp/protocol.py +++ b/aioamqp/protocol.py @@ -190,14 +190,14 @@ def close_ok(self, frame): @asyncio.coroutine def start_connection(self, host, port, login, password, virtualhost, ssl=False, - login_method='AMQPLAIN', insist=False): + login_method='PLAIN', insist=False): """Initiate a connection at the protocol level We send `PROTOCOL_HEADER' """ - if login_method != 'AMQPLAIN': + if login_method != 'PLAIN': # TODO - logger.warning('only AMQPLAIN login_method is supported, falling back to AMQPLAIN') + logger.warning('only PLAIN login_method is supported, falling back to AMQPLAIN') self._stream_writer.write(amqp_constants.PROTOCOL_HEADER) From 00b04c8d9292414157211e6e42fc2b5f407c725b Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Thu, 3 Jan 2019 16:42:54 +0100 Subject: [PATCH 26/72] Update changelog --- docs/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 665c809..ecd7063 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,8 @@ Changelog Next release ------------ + * Uses pamqp to encode or decode protocol frames. + Aioamqp 0.12.0 -------------- From 53f694f7e0087da7081311e551fcbc3b3984e517 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Mon, 8 Oct 2018 17:36:14 +0200 Subject: [PATCH 27/72] Drop both python 3.3 and python 3.4 --- .travis.yml | 2 -- aioamqp/__init__.py | 2 -- docs/changelog.rst | 1 + docs/introduction.rst | 3 +-- setup.cfg | 2 +- setup.py | 5 ----- tox.ini | 4 ++-- 7 files changed, 5 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5399238..2807a2f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ language: python python: -- 3.3 -- 3.4 - 3.5 - 3.6 - 3.7-dev diff --git a/aioamqp/__init__.py b/aioamqp/__init__.py index 9c49fdf..09c594e 100644 --- a/aioamqp/__init__.py +++ b/aioamqp/__init__.py @@ -41,8 +41,6 @@ def connect(host='localhost', port=None, login='guest', password='guest', create_connection_kwargs = {} if ssl: - if sys.version_info < (3, 4): - raise NotImplementedError('SSL not supported on Python 3.3 yet') ssl_context = ssl_module.create_default_context() if not verify_ssl: ssl_context.check_hostname = False diff --git a/docs/changelog.rst b/docs/changelog.rst index ecd7063..4667c21 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,7 @@ Next release ------------ * Uses pamqp to encode or decode protocol frames. + * Drops support of python 3.3 and python 3.4. Aioamqp 0.12.0 -------------- diff --git a/docs/introduction.rst b/docs/introduction.rst index 7038a5a..ec86f61 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -7,8 +7,7 @@ Aioamqp library is a pure-Python implementation of the AMQP 0.9.1 protocol using Prerequisites ------------- -Aioamqp works only with python >= 3.3 using asyncio library. -If your are using Python 3.3 you'll have to install asyncio from pypi, but asyncio is now included in python 3.4 standard library. +Aioamqp works only with python >= 3.5 using asyncio library. Installation ------------ diff --git a/setup.cfg b/setup.cfg index 5729f55..2cc07f0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ [bdist_wheel] -python-tag = py33.py34.py35 +python-tag = py35.py36 diff --git a/setup.py b/setup.py index c7d5415..85e8f11 100644 --- a/setup.py +++ b/setup.py @@ -26,9 +26,6 @@ install_requires=[ 'pamqp>=2.0,<3', ], - extras_require={ - ':python_version=="3.3"': ['asyncio'], - }, classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: Developers", @@ -36,8 +33,6 @@ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", ], diff --git a/tox.ini b/tox.ini index 40eab38..29ebf79 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py33, py34, py35, py36 +envlist = py35, py36 skipsdist = true skip_missing_interpreters = true @@ -8,4 +8,4 @@ whitelist_externals = bash deps = -rrequirements_dev.txt commands = - nosetests \ No newline at end of file + nosetests From 4f4a6d4f3c1a2e333e8980b5a313af1fd512cb64 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Fri, 30 Nov 2018 13:36:00 +0100 Subject: [PATCH 28/72] use pytest as launcher Freeze to pytest 3 until we drop python 3.3 & 3.4 --- Makefile | 6 +++--- requirements_dev.txt | 3 ++- tox.ini | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 45a1782..ab1414f 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,8 @@ PACKAGE = aioamqp -NOSETESTS ?= nosetests -TEST_OPTIONS ?= --verbosity=2 +TEST_LAUNCHER ?= pytest +TEST_OPTIONS ?= -v -s --timeout=20 PYLINT_RC ?= .pylintrc BUILD_DIR ?= build @@ -28,7 +28,7 @@ livehtml: docs sphinx-autobuild $(AUTOSPHINXOPTS) $(ALLSPHINXOPTS) $(SPHINXBUILDDIR) test: - $(NOSETESTS) $(PACKAGE) $(TEST_OPTIONS) + $(TEST_LAUNCHER) $(TEST_OPTIONS) $(PACKAGE) update: diff --git a/requirements_dev.txt b/requirements_dev.txt index 46b56c8..1786cf1 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,8 +1,9 @@ -e . -nose coverage pylint +pytest>4 +pytest-timeout Sphinx sphinx-rtd-theme diff --git a/tox.ini b/tox.ini index 29ebf79..5b21e4b 100644 --- a/tox.ini +++ b/tox.ini @@ -8,4 +8,4 @@ whitelist_externals = bash deps = -rrequirements_dev.txt commands = - nosetests + pytest -v -s From 27f407c84b952a8617ba3b8921bfd6ca6ebf2677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20K=C3=A4ufl?= Date: Thu, 12 Jul 2018 11:36:11 +0200 Subject: [PATCH 29/72] Add classifier for Python 3.7 and add py37 to tox.ini --- setup.cfg | 2 +- setup.py | 1 + tox.ini | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 2cc07f0..9c7463e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ [bdist_wheel] -python-tag = py35.py36 +python-tag = py35.py36.py37 diff --git a/setup.py b/setup.py index 85e8f11..1ed1a36 100644 --- a/setup.py +++ b/setup.py @@ -35,6 +35,7 @@ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", ], platforms='all', license='BSD' diff --git a/tox.ini b/tox.ini index 5b21e4b..ee24073 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35, py36 +envlist = py35, py36, py37 skipsdist = true skip_missing_interpreters = true From 6c9641ae1a98ae5821ac0d4954fddb297ae367fa Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Thu, 3 Jan 2019 17:49:30 +0100 Subject: [PATCH 30/72] Now uses async and await keywords --- aioamqp/__init__.py | 14 +- aioamqp/channel.py | 257 +++++++++------------- aioamqp/frame.py | 9 +- aioamqp/protocol.py | 134 +++++------ aioamqp/tests/test_basic.py | 156 ++++++------- aioamqp/tests/test_channel.py | 78 +++---- aioamqp/tests/test_close.py | 47 ++-- aioamqp/tests/test_connect.py | 21 +- aioamqp/tests/test_connection_close.py | 16 +- aioamqp/tests/test_connection_lost.py | 5 +- aioamqp/tests/test_consume.py | 121 +++++----- aioamqp/tests/test_exchange.py | 104 ++++----- aioamqp/tests/test_heartbeat.py | 7 +- aioamqp/tests/test_properties.py | 47 ++-- aioamqp/tests/test_protocol.py | 32 ++- aioamqp/tests/test_publish.py | 73 +++--- aioamqp/tests/test_queue.py | 146 ++++++------ aioamqp/tests/test_recover.py | 15 +- aioamqp/tests/test_server_basic_cancel.py | 35 ++- aioamqp/tests/testcase.py | 66 +++--- docs/api.rst | 55 ++--- docs/examples/hello_world.rst | 21 +- docs/examples/publish_subscribe.rst | 10 +- docs/examples/routing.rst | 8 +- docs/examples/rpc.rst | 12 +- docs/examples/topics.rst | 8 +- docs/examples/work_queue.rst | 16 +- examples/emit_log.py | 14 +- examples/emit_log_direct.py | 14 +- examples/emit_log_topic.py | 13 +- examples/new_task.py | 15 +- examples/receive.py | 14 +- examples/receive_log.py | 18 +- examples/receive_log_direct.py | 22 +- examples/receive_log_topic.py | 18 +- examples/rpc_client.py | 31 ++- examples/rpc_server.py | 24 +- examples/send.py | 13 +- examples/send_with_return.py | 16 +- examples/worker.py | 23 +- 40 files changed, 750 insertions(+), 998 deletions(-) diff --git a/aioamqp/__init__.py b/aioamqp/__init__.py index 09c594e..057d09a 100644 --- a/aioamqp/__init__.py +++ b/aioamqp/__init__.py @@ -11,8 +11,7 @@ from .version import __packagename__ -@asyncio.coroutine -def connect(host='localhost', port=None, login='guest', password='guest', +async def connect(host='localhost', port=None, login='guest', password='guest', virtualhost='/', ssl=False, login_method='AMQPLAIN', insist=False, protocol_factory=AmqpProtocol, *, verify_ssl=True, loop=None, **kwargs): """Convenient method to connect to an AMQP broker @@ -53,7 +52,7 @@ def connect(host='localhost', port=None, login='guest', password='guest', else: port = 5672 - transport, protocol = yield from loop.create_connection( + transport, protocol = await loop.create_connection( factory, host, port, **create_connection_kwargs ) @@ -66,17 +65,16 @@ def connect(host='localhost', port=None, login='guest', password='guest', sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) try: - yield from protocol.start_connection(host, port, login, password, virtualhost, ssl=ssl, + await protocol.start_connection(host, port, login, password, virtualhost, ssl=ssl, login_method=login_method, insist=insist) except Exception: - yield from protocol.wait_closed() + await protocol.wait_closed() raise return (transport, protocol) -@asyncio.coroutine -def from_url( +async def from_url( url, login_method='AMQPLAIN', insist=False, protocol_factory=AmqpProtocol, *, verify_ssl=True, **kwargs): """ Connect to the AMQP using a single url parameter and return the client. @@ -100,7 +98,7 @@ def from_url( if url.scheme not in ('amqp', 'amqps'): raise ValueError('Invalid protocol %s, valid protocols are amqp or amqps' % url.scheme) - transport, protocol = yield from connect( + transport, protocol = await connect( host=url.hostname or 'localhost', port=url.port, login=url.username or 'guest', diff --git a/aioamqp/channel.py b/aioamqp/channel.py index aeb42f0..0848de5 100644 --- a/aioamqp/channel.py +++ b/aioamqp/channel.py @@ -74,8 +74,7 @@ def connection_closed(self, server_code=None, server_reason=None, exception=None self.protocol.release_channel_id(self.channel_id) self.close_event.set() - @asyncio.coroutine - def dispatch_frame(self, frame): + async def dispatch_frame(self, frame): methods = { pamqp.specification.Channel.OpenOk.name: self.open_ok, pamqp.specification.Channel.FlowOk.name: self.flow_ok, @@ -111,75 +110,67 @@ def dispatch_frame(self, frame): if frame.name not in methods: raise NotImplementedError("Frame %s is not implemented" % frame.name) - yield from methods[frame.name](frame) + await methods[frame.name](frame) - @asyncio.coroutine - def _write_frame(self, channel_id, request, check_open=True, drain=True): - yield from self.protocol.ensure_open() + async def _write_frame(self, channel_id, request, check_open=True, drain=True): + await self.protocol.ensure_open() if not self.is_open and check_open: raise exceptions.ChannelClosed() amqp_frame.write(self.protocol._stream_writer, channel_id, request) if drain: - yield from self.protocol._drain() + await self.protocol._drain() - @asyncio.coroutine - def _write_frame_awaiting_response(self, waiter_id, channel_id, request, no_wait, check_open=True, drain=True): + async def _write_frame_awaiting_response(self, waiter_id, channel_id, request, no_wait, check_open=True, drain=True): '''Write a frame and set a waiter for the response (unless no_wait is set)''' if no_wait: - yield from self._write_frame(channel_id, request, check_open=check_open, drain=drain) + await self._write_frame(channel_id, request, check_open=check_open, drain=drain) return None f = self._set_waiter(waiter_id) try: - yield from self._write_frame(channel_id, request, check_open=check_open, drain=drain) + await self._write_frame(channel_id, request, check_open=check_open, drain=drain) except Exception: self._get_waiter(waiter_id) f.cancel() raise - return (yield from f) + return (await f) # ## Channel class implementation # - @asyncio.coroutine - def open(self): + async def open(self): """Open the channel on the server.""" request = pamqp.specification.Channel.Open() - return (yield from self._write_frame_awaiting_response( + return (await self._write_frame_awaiting_response( 'open', self.channel_id, request, no_wait=False, check_open=False)) - @asyncio.coroutine - def open_ok(self, frame): + async def open_ok(self, frame): self.close_event.clear() fut = self._get_waiter('open') fut.set_result(True) logger.debug("Channel is open") - @asyncio.coroutine - def close(self, reply_code=0, reply_text="Normal Shutdown"): + async def close(self, reply_code=0, reply_text="Normal Shutdown"): """Close the channel.""" if not self.is_open: raise exceptions.ChannelClosed("channel already closed or closing") self.close_event.set() request = pamqp.specification.Channel.Close(reply_code, reply_text, class_id=0, method_id=0) - return (yield from self._write_frame_awaiting_response( + return (await self._write_frame_awaiting_response( 'close', self.channel_id, request, no_wait=False, check_open=False)) - @asyncio.coroutine - def close_ok(self, frame): + async def close_ok(self, frame): self._get_waiter('close').set_result(True) logger.info("Channel closed") self.protocol.release_channel_id(self.channel_id) - @asyncio.coroutine - def _send_channel_close_ok(self): + async def _send_channel_close_ok(self): request = pamqp.specification.Channel.CloseOk() - yield from self._write_frame(self.channel_id, request) + await self._write_frame(self.channel_id, request) - @asyncio.coroutine - def server_channel_close(self, frame): - yield from self._send_channel_close_ok() + async def server_channel_close(self, frame): + await self._send_channel_close_ok() results = { 'reply_code': frame.reply_code, 'reply_text': frame.reply_text, @@ -188,15 +179,13 @@ def server_channel_close(self, frame): } self.connection_closed(results['reply_code'], results['reply_text']) - @asyncio.coroutine - def flow(self, active): + async def flow(self, active): request = pamqp.specification.Channel.Flow(active) - return (yield from self._write_frame_awaiting_response( + return (await self._write_frame_awaiting_response( 'flow', self.channel_id, request, no_wait=False, check_open=False)) - @asyncio.coroutine - def flow_ok(self, frame): + async def flow_ok(self, frame): self.close_event.clear() fut = self._get_waiter('flow') fut.set_result({'active': frame.active}) @@ -207,8 +196,7 @@ def flow_ok(self, frame): ## Exchange class implementation # - @asyncio.coroutine - def exchange_declare(self, exchange_name, type_name, passive=False, durable=False, + async def exchange_declare(self, exchange_name, type_name, passive=False, durable=False, auto_delete=False, no_wait=False, arguments=None): request = pamqp.specification.Exchange.Declare( exchange=exchange_name, @@ -220,30 +208,26 @@ def exchange_declare(self, exchange_name, type_name, passive=False, durable=Fals arguments=arguments ) - return (yield from self._write_frame_awaiting_response( + return (await self._write_frame_awaiting_response( 'exchange_declare', self.channel_id, request, no_wait)) - @asyncio.coroutine - def exchange_declare_ok(self, frame): + async def exchange_declare_ok(self, frame): future = self._get_waiter('exchange_declare') future.set_result(True) logger.debug("Exchange declared") return future - @asyncio.coroutine - def exchange_delete(self, exchange_name, if_unused=False, no_wait=False): + async def exchange_delete(self, exchange_name, if_unused=False, no_wait=False): request = pamqp.specification.Exchange.Delete(exchange=exchange_name, if_unused=if_unused, nowait=no_wait) - return (yield from self._write_frame_awaiting_response( + return (await self._write_frame_awaiting_response( 'exchange_delete', self.channel_id, request, no_wait)) - @asyncio.coroutine - def exchange_delete_ok(self, frame): + async def exchange_delete_ok(self, frame): future = self._get_waiter('exchange_delete') future.set_result(True) logger.debug("Exchange deleted") - @asyncio.coroutine - def exchange_bind(self, exchange_destination, exchange_source, routing_key, + async def exchange_bind(self, exchange_destination, exchange_source, routing_key, no_wait=False, arguments=None): if arguments is None: arguments = {} @@ -254,17 +238,15 @@ def exchange_bind(self, exchange_destination, exchange_source, routing_key, nowait=no_wait, arguments=arguments ) - return (yield from self._write_frame_awaiting_response( + return (await self._write_frame_awaiting_response( 'exchange_bind', self.channel_id, request, no_wait)) - @asyncio.coroutine - def exchange_bind_ok(self, frame): + async def exchange_bind_ok(self, frame): future = self._get_waiter('exchange_bind') future.set_result(True) logger.debug("Exchange bound") - @asyncio.coroutine - def exchange_unbind(self, exchange_destination, exchange_source, routing_key, + async def exchange_unbind(self, exchange_destination, exchange_source, routing_key, no_wait=False, arguments=None): if arguments is None: arguments = {} @@ -276,11 +258,10 @@ def exchange_unbind(self, exchange_destination, exchange_source, routing_key, nowait=no_wait, arguments=arguments, ) - return (yield from self._write_frame_awaiting_response( + return (await self._write_frame_awaiting_response( 'exchange_unbind', self.channel_id, request, no_wait)) - @asyncio.coroutine - def exchange_unbind_ok(self, frame): + async def exchange_unbind_ok(self, frame): future = self._get_waiter('exchange_unbind') future.set_result(True) logger.debug("Exchange bound") @@ -289,8 +270,7 @@ def exchange_unbind_ok(self, frame): ## Queue class implementation # - @asyncio.coroutine - def queue_declare(self, queue_name=None, passive=False, durable=False, + async def queue_declare(self, queue_name=None, passive=False, durable=False, exclusive=False, auto_delete=False, no_wait=False, arguments=None): """Create or check a queue on the broker Args: @@ -322,11 +302,10 @@ def queue_declare(self, queue_name=None, passive=False, durable=False, nowait=no_wait, arguments=arguments ) - return (yield from self._write_frame_awaiting_response( + return (await self._write_frame_awaiting_response( 'queue_declare', self.channel_id, request, no_wait)) - @asyncio.coroutine - def queue_declare_ok(self, frame): + async def queue_declare_ok(self, frame): results = { 'queue': frame.queue, 'message_count': frame.message_count, @@ -337,8 +316,7 @@ def queue_declare_ok(self, frame): logger.debug("Queue declared") - @asyncio.coroutine - def queue_delete(self, queue_name, if_unused=False, if_empty=False, no_wait=False): + async def queue_delete(self, queue_name, if_unused=False, if_empty=False, no_wait=False): """Delete a queue in RabbitMQ Args: queue_name: str, the queue to receive message from @@ -352,17 +330,15 @@ def queue_delete(self, queue_name, if_unused=False, if_empty=False, no_wait=Fals if_empty=if_empty, nowait=no_wait ) - return (yield from self._write_frame_awaiting_response( + return (await self._write_frame_awaiting_response( 'queue_delete', self.channel_id, request, no_wait)) - @asyncio.coroutine - def queue_delete_ok(self, frame): + async def queue_delete_ok(self, frame): future = self._get_waiter('queue_delete') future.set_result(True) logger.debug("Queue deleted") - @asyncio.coroutine - def queue_bind(self, queue_name, exchange_name, routing_key, no_wait=False, arguments=None): + async def queue_bind(self, queue_name, exchange_name, routing_key, no_wait=False, arguments=None): """Bind a queue and a channel.""" if arguments is None: arguments = {} @@ -375,17 +351,15 @@ def queue_bind(self, queue_name, exchange_name, routing_key, no_wait=False, argu arguments=arguments ) # short reserved-1 - return (yield from self._write_frame_awaiting_response( + return (await self._write_frame_awaiting_response( 'queue_bind', self.channel_id, request, no_wait)) - @asyncio.coroutine - def queue_bind_ok(self, frame): + async def queue_bind_ok(self, frame): future = self._get_waiter('queue_bind') future.set_result(True) logger.debug("Queue bound") - @asyncio.coroutine - def queue_unbind(self, queue_name, exchange_name, routing_key, arguments=None): + async def queue_unbind(self, queue_name, exchange_name, routing_key, arguments=None): if arguments is None: arguments = {} @@ -396,25 +370,22 @@ def queue_unbind(self, queue_name, exchange_name, routing_key, arguments=None): arguments=arguments ) - return (yield from self._write_frame_awaiting_response( + return (await self._write_frame_awaiting_response( 'queue_unbind', self.channel_id, request, no_wait=False)) - @asyncio.coroutine - def queue_unbind_ok(self, frame): + async def queue_unbind_ok(self, frame): future = self._get_waiter('queue_unbind') future.set_result(True) logger.debug("Queue unbound") - @asyncio.coroutine - def queue_purge(self, queue_name, no_wait=False): + async def queue_purge(self, queue_name, no_wait=False): request = pamqp.specification.Queue.Purge( queue=queue_name, nowait=no_wait ) - return (yield from self._write_frame_awaiting_response( + return (await self._write_frame_awaiting_response( 'queue_purge', self.channel_id, request, no_wait=no_wait)) - @asyncio.coroutine - def queue_purge_ok(self, frame): + async def queue_purge_ok(self, frame): future = self._get_waiter('queue_purge') future.set_result({'message_count': frame.message_count}) @@ -422,8 +393,7 @@ def queue_purge_ok(self, frame): ## Basic class implementation # - @asyncio.coroutine - def basic_publish(self, payload, exchange_name, routing_key, properties=None, mandatory=False, immediate=False): + async def basic_publish(self, payload, exchange_name, routing_key, properties=None, mandatory=False, immediate=False): if isinstance(payload, str): warnings.warn("Str payload support will be removed in next release", DeprecationWarning) payload = payload.encode() @@ -438,25 +408,24 @@ def basic_publish(self, payload, exchange_name, routing_key, properties=None, ma immediate=immediate ) - yield from self._write_frame(self.channel_id, method_request, drain=False) + await self._write_frame(self.channel_id, method_request, drain=False) header_request = pamqp.header.ContentHeader( body_size=len(payload), properties=pamqp.specification.Basic.Properties(**properties) ) - yield from self._write_frame(self.channel_id, header_request, drain=False) + await self._write_frame(self.channel_id, header_request, drain=False) # split the payload frame_max = self.protocol.server_frame_max or len(payload) for chunk in (payload[0+i:frame_max+i] for i in range(0, len(payload), frame_max)): content_request = pamqp.body.ContentBody(chunk) - yield from self._write_frame(self.channel_id, content_request, drain=False) + await self._write_frame(self.channel_id, content_request, drain=False) - yield from self.protocol._drain() + await self.protocol._drain() - @asyncio.coroutine - def basic_qos(self, prefetch_size=0, prefetch_count=0, connection_global=False): + async def basic_qos(self, prefetch_size=0, prefetch_count=0, connection_global=False): """Specifies quality of service. Args: @@ -475,27 +444,24 @@ def basic_qos(self, prefetch_size=0, prefetch_count=0, connection_global=False): request = pamqp.specification.Basic.Qos( prefetch_size, prefetch_count, connection_global ) - return (yield from self._write_frame_awaiting_response( + return (await self._write_frame_awaiting_response( 'basic_qos', self.channel_id, request, no_wait=False) ) - @asyncio.coroutine - def basic_qos_ok(self, frame): + async def basic_qos_ok(self, frame): future = self._get_waiter('basic_qos') future.set_result(True) logger.debug("Qos ok") - @asyncio.coroutine - def basic_server_nack(self, frame, delivery_tag=None): + async def basic_server_nack(self, frame, delivery_tag=None): if delivery_tag is None: delivery_tag = frame.delivery_tag fut = self._get_waiter('basic_server_ack_{}'.format(delivery_tag)) logger.debug('Received nack for delivery tag %r', delivery_tag) fut.set_exception(exceptions.PublishFailed(delivery_tag)) - @asyncio.coroutine - def basic_consume(self, callback, queue_name='', consumer_tag='', no_local=False, no_ack=False, + async def basic_consume(self, callback, queue_name='', consumer_tag='', no_local=False, no_ack=False, exclusive=False, no_wait=False, arguments=None): """Starts the consumption of message into a queue. the callback will be called each time we're receiving a message. @@ -532,7 +498,7 @@ def basic_consume(self, callback, queue_name='', consumer_tag='', no_local=False self.consumer_callbacks[consumer_tag] = callback self.last_consumer_tag = consumer_tag - return_value = yield from self._write_frame_awaiting_response( + return_value = await self._write_frame_awaiting_response( 'basic_consume', self.channel_id, request, no_wait) if no_wait: return_value = {'consumer_tag': consumer_tag} @@ -540,8 +506,7 @@ def basic_consume(self, callback, queue_name='', consumer_tag='', no_local=False self._ctag_events[consumer_tag].set() return return_value - @asyncio.coroutine - def basic_consume_ok(self, frame): + async def basic_consume_ok(self, frame): ctag = frame.consumer_tag results = { 'consumer_tag': ctag, @@ -550,19 +515,18 @@ def basic_consume_ok(self, frame): future.set_result(results) self._ctag_events[ctag] = asyncio.Event(loop=self._loop) - @asyncio.coroutine - def basic_deliver(self, frame): + async def basic_deliver(self, frame): consumer_tag = frame.consumer_tag delivery_tag = frame.delivery_tag is_redeliver = frame.redelivered exchange_name = frame.exchange routing_key = frame.routing_key - _channel, content_header_frame = yield from self.protocol.get_frame() + _channel, content_header_frame = await self.protocol.get_frame() buffer = io.BytesIO() while(buffer.tell() < content_header_frame.body_size): - _channel, content_body_frame = yield from self.protocol.get_frame() + _channel, content_body_frame = await self.protocol.get_frame() buffer.write(content_body_frame.value) body = buffer.getvalue() @@ -573,13 +537,12 @@ def basic_deliver(self, frame): event = self._ctag_events.get(consumer_tag) if event: - yield from event.wait() + await event.wait() del self._ctag_events[consumer_tag] - yield from callback(self, body, envelope, properties) + await callback(self, body, envelope, properties) - @asyncio.coroutine - def server_basic_cancel(self, frame): + async def server_basic_cancel(self, frame): # https://www.rabbitmq.com/consumer-cancel.html consumer_tag = frame.consumer_tag _no_wait = frame.nowait @@ -587,20 +550,18 @@ def server_basic_cancel(self, frame): logger.info("consume cancelled received") for callback in self.cancellation_callbacks: try: - yield from callback(self, consumer_tag) + await callback(self, consumer_tag) except Exception as error: # pylint: disable=broad-except logger.error("cancellation callback %r raised exception %r", callback, error) - @asyncio.coroutine - def basic_cancel(self, consumer_tag, no_wait=False): + async def basic_cancel(self, consumer_tag, no_wait=False): request = pamqp.specification.Basic.Cancel(consumer_tag, no_wait) - return (yield from self._write_frame_awaiting_response( + return (await self._write_frame_awaiting_response( 'basic_cancel', self.channel_id, request, no_wait=no_wait) ) - @asyncio.coroutine - def basic_cancel_ok(self, frame): + async def basic_cancel_ok(self, frame): results = { 'consumer_tag': frame.consumer_tag, } @@ -608,15 +569,13 @@ def basic_cancel_ok(self, frame): future.set_result(results) logger.debug("Cancel ok") - @asyncio.coroutine - def basic_get(self, queue_name='', no_ack=False): + async def basic_get(self, queue_name='', no_ack=False): request = pamqp.specification.Basic.Get(queue=queue_name, no_ack=no_ack) - return (yield from self._write_frame_awaiting_response( + return (await self._write_frame_awaiting_response( 'basic_get', self.channel_id, request, no_wait=False) ) - @asyncio.coroutine - def basic_get_ok(self, frame): + async def basic_get_ok(self, frame): data = { 'delivery_tag': frame.delivery_tag, 'redelivered': frame.redelivered, @@ -625,11 +584,11 @@ def basic_get_ok(self, frame): 'message_count': frame.message_count, } - _channel, content_header_frame = yield from self.protocol.get_frame() + _channel, content_header_frame = await self.protocol.get_frame() buffer = io.BytesIO() while(buffer.tell() < content_header_frame.body_size): - _channel, content_body_frame = yield from self.protocol.get_frame() + _channel, content_body_frame = await self.protocol.get_frame() buffer.write(content_body_frame.value) data['message'] = buffer.getvalue() @@ -637,63 +596,54 @@ def basic_get_ok(self, frame): future = self._get_waiter('basic_get') future.set_result(data) - @asyncio.coroutine - def basic_get_empty(self, frame): + async def basic_get_empty(self, frame): future = self._get_waiter('basic_get') future.set_exception(exceptions.EmptyQueue) - @asyncio.coroutine - def basic_client_ack(self, delivery_tag, multiple=False): + async def basic_client_ack(self, delivery_tag, multiple=False): request = pamqp.specification.Basic.Ack(delivery_tag, multiple) - yield from self._write_frame(self.channel_id, request) + await self._write_frame(self.channel_id, request) - @asyncio.coroutine - def basic_client_nack(self, delivery_tag, multiple=False, requeue=True): + async def basic_client_nack(self, delivery_tag, multiple=False, requeue=True): request = pamqp.specification.Basic.Nack(delivery_tag, multiple, requeue) - yield from self._write_frame(self.channel_id, request) + await self._write_frame(self.channel_id, request) - @asyncio.coroutine - def basic_server_ack(self, frame): + async def basic_server_ack(self, frame): delivery_tag = frame.delivery_tag fut = self._get_waiter('basic_server_ack_{}'.format(delivery_tag)) logger.debug('Received ack for delivery tag %s', delivery_tag) fut.set_result(True) - @asyncio.coroutine - def basic_reject(self, delivery_tag, requeue=False): + async def basic_reject(self, delivery_tag, requeue=False): request = pamqp.specification.Basic.Reject(delivery_tag, requeue) - yield from self._write_frame(self.channel_id, request) + await self._write_frame(self.channel_id, request) - @asyncio.coroutine - def basic_recover_async(self, requeue=True): + async def basic_recover_async(self, requeue=True): request = pamqp.specification.Basic.RecoverAsync(requeue) - yield from self._write_frame(self.channel_id, request) + await self._write_frame(self.channel_id, request) - @asyncio.coroutine - def basic_recover(self, requeue=True): + async def basic_recover(self, requeue=True): request = pamqp.specification.Basic.Recover(requeue) - return (yield from self._write_frame_awaiting_response( + return (await self._write_frame_awaiting_response( 'basic_recover', self.channel_id, request, no_wait=False) ) - @asyncio.coroutine - def basic_recover_ok(self, frame): + async def basic_recover_ok(self, frame): future = self._get_waiter('basic_recover') future.set_result(True) logger.debug("Cancel ok") - @asyncio.coroutine - def basic_return(self, frame): + async def basic_return(self, frame): reply_code = frame.reply_code reply_text = frame.reply_text exchange_name = frame.exchange routing_key = frame.routing_key - _channel, content_header_frame = yield from self.protocol.get_frame() + _channel, content_header_frame = await self.protocol.get_frame() buffer = io.BytesIO() while(buffer.tell() < content_header_frame.body_size): - _channel, content_body_frame = yield from self.protocol.get_frame() + _channel, content_body_frame = await self.protocol.get_frame() buffer.write(content_body_frame.value) body = buffer.getvalue() @@ -706,7 +656,7 @@ def basic_return(self, frame): logger.warning('You have received a returned message, but dont have a callback registered for returns.' ' Please set channel.return_callback') else: - yield from callback(self, body, envelope, properties) + await callback(self, body, envelope, properties) # @@ -715,8 +665,7 @@ def basic_return(self, frame): queue = queue_declare exchange = exchange_declare - @asyncio.coroutine - def publish(self, payload, exchange_name, routing_key, properties=None, mandatory=False, immediate=False): + async def publish(self, payload, exchange_name, routing_key, properties=None, mandatory=False, immediate=False): if isinstance(payload, str): warnings.warn("Str payload support will be removed in next release", DeprecationWarning) payload = payload.encode() @@ -734,38 +683,36 @@ def publish(self, payload, exchange_name, routing_key, properties=None, mandator mandatory=mandatory, immediate=immediate ) - yield from self._write_frame(self.channel_id, method_request, drain=False) + await self._write_frame(self.channel_id, method_request, drain=False) properties = pamqp.specification.Basic.Properties(**properties) header_request = pamqp.header.ContentHeader( body_size=len(payload), properties=properties ) - yield from self._write_frame(self.channel_id, header_request, drain=False) + await self._write_frame(self.channel_id, header_request, drain=False) # split the payload frame_max = self.protocol.server_frame_max or len(payload) for chunk in (payload[0+i:frame_max+i] for i in range(0, len(payload), frame_max)): content_request = pamqp.body.ContentBody(chunk) - yield from self._write_frame(self.channel_id, content_request, drain=False) + await self._write_frame(self.channel_id, content_request, drain=False) - yield from self.protocol._drain() + await self.protocol._drain() if self.publisher_confirms: - yield from fut + await fut - @asyncio.coroutine - def confirm_select(self, *, no_wait=False): + async def confirm_select(self, *, no_wait=False): if self.publisher_confirms: raise ValueError('publisher confirms already enabled') request = pamqp.specification.Confirm.Select(nowait=no_wait) - return (yield from self._write_frame_awaiting_response( + return (await self._write_frame_awaiting_response( 'confirm_select', self.channel_id, request, no_wait) ) - @asyncio.coroutine - def confirm_select_ok(self, frame): + async def confirm_select_ok(self, frame): self.publisher_confirms = True self.delivery_tag_iter = count(1) fut = self._get_waiter('confirm_select') diff --git a/aioamqp/frame.py b/aioamqp/frame.py index c3ce218..bbd3db4 100644 --- a/aioamqp/frame.py +++ b/aioamqp/frame.py @@ -64,8 +64,7 @@ def write(writer, channel, encoder): return writer.write(pamqp.frame.marshal(encoder, channel)) -@asyncio.coroutine -def read(reader): +async def read(reader): """Read a new frame from the wire reader: asyncio StreamReader @@ -76,13 +75,13 @@ def read(reader): if not reader: raise exceptions.AmqpClosedConnection() try: - data = yield from reader.readexactly(7) + data = await reader.readexactly(7) except (asyncio.IncompleteReadError, socket.error) as ex: raise exceptions.AmqpClosedConnection() from ex frame_type, channel, frame_length = pamqp.frame._frame_parts(data) - payload_data = yield from reader.readexactly(frame_length) + payload_data = await reader.readexactly(frame_length) frame = None if frame_type == amqp_constants.TYPE_METHOD: @@ -97,6 +96,6 @@ def read(reader): elif frame_type == amqp_constants.TYPE_HEARTBEAT: frame = pamqp.heartbeat.Heartbeat() - frame_end = yield from reader.readexactly(1) + frame_end = await reader.readexactly(1) assert frame_end == amqp_constants.FRAME_END return channel, frame diff --git a/aioamqp/protocol.py b/aioamqp/protocol.py index 1433393..6578b30 100644 --- a/aioamqp/protocol.py +++ b/aioamqp/protocol.py @@ -125,8 +125,7 @@ def data_received(self, data): self._heartbeat_timer_recv_reset() super().data_received(data) - @asyncio.coroutine - def ensure_open(self): + async def ensure_open(self): # Raise a suitable exception if the connection isn't open. # Handle cases from the most common to the least common. @@ -138,31 +137,28 @@ def ensure_open(self): # If the closing handshake is in progress, let it complete. if self.state == CLOSING: - yield from self.wait_closed() + await self.wait_closed() raise exceptions.AmqpClosedConnection() # Control may only reach this point in buggy third-party subclasses. assert self.state == CONNECTING raise exceptions.AioamqpException("connection isn't established yet.") - @asyncio.coroutine - def _drain(self): - with (yield from self._drain_lock): + async def _drain(self): + with (await self._drain_lock): # drain() cannot be called concurrently by multiple coroutines: # http://bugs.python.org/issue29930. Remove this lock when no # version of Python where this bugs exists is supported anymore. - yield from self._stream_writer.drain() + await self._stream_writer.drain() - @asyncio.coroutine - def _write_frame(self, channel_id, request, drain=True): + async def _write_frame(self, channel_id, request, drain=True): amqp_frame.write(self._stream_writer, channel_id, request) if drain: - yield from self._drain() + await self._drain() - @asyncio.coroutine - def close(self, no_wait=False, timeout=None): + async def close(self, no_wait=False, timeout=None): """Close connection (and all channels)""" - yield from self.ensure_open() + await self.ensure_open() self.state = CLOSING request = pamqp.specification.Connection.Close( reply_code=0, @@ -171,25 +167,22 @@ def close(self, no_wait=False, timeout=None): method_id=0 ) - yield from self._write_frame(0, request) + await self._write_frame(0, request) if not no_wait: - yield from self.wait_closed(timeout=timeout) + await self.wait_closed(timeout=timeout) - @asyncio.coroutine - def wait_closed(self, timeout=None): - yield from asyncio.wait_for(self.connection_closed.wait(), timeout=timeout, loop=self._loop) + async def wait_closed(self, timeout=None): + await asyncio.wait_for(self.connection_closed.wait(), timeout=timeout, loop=self._loop) if self._heartbeat_worker is not None: try: - yield from asyncio.wait_for(self._heartbeat_worker, timeout=timeout, loop=self._loop) + await asyncio.wait_for(self._heartbeat_worker, timeout=timeout, loop=self._loop) except asyncio.CancelledError: pass - @asyncio.coroutine - def close_ok(self, frame): + async def close_ok(self, frame): self._stream_writer.close() - @asyncio.coroutine - def start_connection(self, host, port, login, password, virtualhost, ssl=False, + async def start_connection(self, host, port, login, password, virtualhost, ssl=False, login_method='PLAIN', insist=False): """Initiate a connection at the protocol level We send `PROTOCOL_HEADER' @@ -202,7 +195,7 @@ def start_connection(self, host, port, login, password, virtualhost, ssl=False, self._stream_writer.write(amqp_constants.PROTOCOL_HEADER) # Wait 'start' method from the server - yield from self.dispatch_frame() + await self.dispatch_frame() client_properties = { 'capabilities': { @@ -220,10 +213,10 @@ def start_connection(self, host, port, login, password, virtualhost, ssl=False, } # waiting reply start with credentions and co - yield from self.start_ok(client_properties, 'PLAIN', auth, self.server_locales) + await self.start_ok(client_properties, 'PLAIN', auth, self.server_locales) # wait for a "tune" reponse - yield from self.dispatch_frame() + await self.dispatch_frame() tune_ok = { 'channel_max': self.connection_tunning.get('channel_max', self.server_channel_max), @@ -231,7 +224,7 @@ def start_connection(self, host, port, login, password, virtualhost, ssl=False, 'heartbeat': self.connection_tunning.get('heartbeat', self.server_heartbeat), } # "tune" the connexion with max channel, max frame, heartbeat - yield from self.tune_ok(**tune_ok) + await self.tune_ok(**tune_ok) # update connection tunning values self.server_frame_max = tune_ok['frame_max'] @@ -243,25 +236,23 @@ def start_connection(self, host, port, login, password, virtualhost, ssl=False, self._heartbeat_timer_send_reset() # open a virtualhost - yield from self.open(virtualhost, capabilities='', insist=insist) + await self.open(virtualhost, capabilities='', insist=insist) # wait for open-ok - channel, frame = yield from self.get_frame() - yield from self.dispatch_frame(channel, frame) + channel, frame = await self.get_frame() + await self.dispatch_frame(channel, frame) - yield from self.ensure_open() + await self.ensure_open() # for now, we read server's responses asynchronously self.worker = ensure_future(self.run(), loop=self._loop) - @asyncio.coroutine - def get_frame(self): + async def get_frame(self): """Read the frame, and only decode its header """ - return amqp_frame.read(self._stream_reader) + return await amqp_frame.read(self._stream_reader) - @asyncio.coroutine - def dispatch_frame(self, frame_channel=None, frame=None): + async def dispatch_frame(self, frame_channel=None, frame=None): """Dispatch the received frame to the corresponding handler""" method_dispatch = { @@ -272,7 +263,7 @@ def dispatch_frame(self, frame_channel=None, frame=None): pamqp.specification.Connection.OpenOk.name: self.open_ok, } if frame_channel is None and frame is None: - frame_channel, frame = yield from self.get_frame() + frame_channel, frame = await self.get_frame() if isinstance(frame, pamqp.heartbeat.Heartbeat): return @@ -280,7 +271,7 @@ def dispatch_frame(self, frame_channel=None, frame=None): if frame_channel is not 0: channel = self.channels.get(frame_channel) if channel is not None: - yield from channel.dispatch_frame(frame) + await channel.dispatch_frame(frame) else: logger.info("Unknown channel %s", frame_channel) return @@ -288,7 +279,7 @@ def dispatch_frame(self, frame_channel=None, frame=None): if frame.name not in method_dispatch: logger.info("frame %s is not handled", frame.name) return - yield from method_dispatch[frame.name](frame) + await method_dispatch[frame.name](frame) def release_channel_id(self, channel_id): """Called from the channel instance, it relase a previously used @@ -321,11 +312,10 @@ def _close_channels(self, reply_code=None, reply_text=None, exception=None): for channel in self.channels.values(): channel.connection_closed(reply_code, reply_text, exception) - @asyncio.coroutine - def run(self): + async def run(self): while not self.stop_now.done(): try: - yield from self.dispatch_frame() + await self.dispatch_frame() except exceptions.AmqpClosedConnection as exc: logger.info("Close connection") self.stop_now.set_result(None) @@ -334,24 +324,22 @@ def run(self): except Exception: # pylint: disable=broad-except logger.exception('error on dispatch') - @asyncio.coroutine - def heartbeat(self): + async def heartbeat(self): """ deprecated heartbeat coroutine This coroutine is now a no-op as the heartbeat is handled directly by the rest of the AmqpProtocol class. This is kept around for backwards compatibility purposes only. """ - yield from self.stop_now + await self.stop_now - @asyncio.coroutine - def send_heartbeat(self): + async def send_heartbeat(self): """Sends an heartbeat message. It can be an ack for the server or the client willing to check for the connexion timeout """ request = pamqp.heartbeat.Heartbeat() - yield from self._write_frame(0, request) + await self._write_frame(0, request) def _heartbeat_timer_recv_timeout(self): # 4.2.7 If a peer detects no incoming traffic (i.e. received octets) for @@ -390,16 +378,14 @@ def _heartbeat_stop(self): if self._heartbeat_worker is not None: self._heartbeat_worker.cancel() - @asyncio.coroutine - def _heartbeat(self): + async def _heartbeat(self): while self.state != CLOSED: - yield from self._heartbeat_trigger_send.wait() + await self._heartbeat_trigger_send.wait() self._heartbeat_trigger_send.clear() - yield from self.send_heartbeat() + await self.send_heartbeat() # Amqp specific methods - @asyncio.coroutine - def start(self, frame): + async def start(self, frame): """Method sent from the server to begin a new connection""" self.version_major = frame.version_major self.version_minor = frame.version_minor @@ -407,8 +393,7 @@ def start(self, frame): self.server_mechanisms = frame.mechanisms self.server_locales = frame.locales - @asyncio.coroutine - def start_ok(self, client_properties, mechanism, auth, locale): + async def start_ok(self, client_properties, mechanism, auth, locale): def credentials(): return '\0{LOGIN}\0{PASSWORD}'.format(**auth) @@ -418,10 +403,9 @@ def credentials(): locale=locale, response=credentials() ) - yield from self._write_frame(0, request) + await self._write_frame(0, request) - @asyncio.coroutine - def server_close(self, frame): + async def server_close(self, frame): """The server is closing the connection""" self.state = CLOSING reply_code = frame.reply_code @@ -431,40 +415,35 @@ def server_close(self, frame): logger.warning("Server closed connection: %s, code=%s, class_id=%s, method_id=%s", reply_text, reply_code, class_id, method_id) self._close_channels(reply_code, reply_text) - self._close_ok() + await self._close_ok() self._stream_writer.close() - def _close_ok(self): + async def _close_ok(self): request = pamqp.specification.Connection.CloseOk() - yield from self._write_frame(0, request) + await self._write_frame(0, request) - @asyncio.coroutine - def tune(self, frame): + async def tune(self, frame): self.server_channel_max = frame.channel_max self.server_frame_max = frame.frame_max self.server_heartbeat = frame.heartbeat - @asyncio.coroutine - def tune_ok(self, channel_max, frame_max, heartbeat): + async def tune_ok(self, channel_max, frame_max, heartbeat): request = pamqp.specification.Connection.TuneOk( channel_max, frame_max, heartbeat ) - yield from self._write_frame(0, request) + await self._write_frame(0, request) - @asyncio.coroutine - def secure_ok(self, login_response): + async def secure_ok(self, login_response): pass - @asyncio.coroutine - def open(self, virtual_host, capabilities='', insist=False): + async def open(self, virtual_host, capabilities='', insist=False): """Open connection to virtual host.""" request = pamqp.specification.Connection.Open( virtual_host, capabilities, insist ) - yield from self._write_frame(0, request) + await self._write_frame(0, request) - @asyncio.coroutine - def open_ok(self, frame): + async def open_ok(self, frame): self.state = OPEN logger.info("Recv open ok") @@ -472,12 +451,11 @@ def open_ok(self, frame): ## aioamqp public methods # - @asyncio.coroutine - def channel(self, **kwargs): + async def channel(self, **kwargs): """Factory to create a new channel """ - yield from self.ensure_open() + await self.ensure_open() try: channel_id = self.channels_ids_free.pop() except KeyError: @@ -489,5 +467,5 @@ def channel(self, **kwargs): channel_id = self.channels_ids_ceil channel = self.CHANNEL_FACTORY(self, channel_id, **kwargs) self.channels[channel_id] = channel - yield from channel.open() + await channel.open() return channel diff --git a/aioamqp/tests/test_basic.py b/aioamqp/tests/test_basic.py index 7e88a2f..906ceb9 100644 --- a/aioamqp/tests/test_basic.py +++ b/aioamqp/tests/test_basic.py @@ -14,34 +14,30 @@ class QosTestCase(testcase.RabbitTestCase, unittest.TestCase): - @testing.coroutine - def test_basic_qos_default_args(self): - result = yield from self.channel.basic_qos() + async def test_basic_qos_default_args(self): + result = await self.channel.basic_qos() self.assertTrue(result) - @testing.coroutine - def test_basic_qos(self): - result = yield from self.channel.basic_qos( + async def test_basic_qos(self): + result = await self.channel.basic_qos( prefetch_size=0, prefetch_count=100, connection_global=False) self.assertTrue(result) - @testing.coroutine - def test_basic_qos_prefetch_size(self): + async def test_basic_qos_prefetch_size(self): with self.assertRaises(exceptions.ChannelClosed) as cm: - yield from self.channel.basic_qos( + await self.channel.basic_qos( prefetch_size=10, prefetch_count=100, connection_global=False) self.assertEqual(cm.exception.code, 540) - @testing.coroutine - def test_basic_qos_wrong_values(self): + async def test_basic_qos_wrong_values(self): with self.assertRaises(TypeError): - yield from self.channel.basic_qos( + await self.channel.basic_qos( prefetch_size=100000, prefetch_count=1000000000, connection_global=False) @@ -49,52 +45,48 @@ def test_basic_qos_wrong_values(self): class BasicCancelTestCase(testcase.RabbitTestCase, unittest.TestCase): - @testing.coroutine - def test_basic_cancel(self): + async def test_basic_cancel(self): - @asyncio.coroutine - def callback(channel, body, envelope, _properties): + async def callback(channel, body, envelope, _properties): pass queue_name = 'queue_name' exchange_name = 'exchange_name' - yield from self.channel.queue_declare(queue_name) - yield from self.channel.exchange_declare(exchange_name, type_name='direct') - yield from self.channel.queue_bind(queue_name, exchange_name, routing_key='') - result = yield from self.channel.basic_consume(callback, queue_name=queue_name) - result = yield from self.channel.basic_cancel(result['consumer_tag']) + await self.channel.queue_declare(queue_name) + await self.channel.exchange_declare(exchange_name, type_name='direct') + await self.channel.queue_bind(queue_name, exchange_name, routing_key='') + result = await self.channel.basic_consume(callback, queue_name=queue_name) + result = await self.channel.basic_cancel(result['consumer_tag']) - result = yield from self.channel.publish("payload", exchange_name, routing_key='') + result = await self.channel.publish("payload", exchange_name, routing_key='') - yield from asyncio.sleep(5, loop=self.loop) + await asyncio.sleep(5, loop=self.loop) - result = yield from self.channel.queue_declare(queue_name, passive=True) + result = await self.channel.queue_declare(queue_name, passive=True) self.assertEqual(result['message_count'], 1) self.assertEqual(result['consumer_count'], 0) - @testing.coroutine - def test_basic_cancel_unknown_ctag(self): - result = yield from self.channel.basic_cancel("unknown_ctag") + async def test_basic_cancel_unknown_ctag(self): + result = await self.channel.basic_cancel("unknown_ctag") self.assertTrue(result) class BasicGetTestCase(testcase.RabbitTestCase, unittest.TestCase): - @testing.coroutine - def test_basic_get(self): + async def test_basic_get(self): queue_name = 'queue_name' exchange_name = 'exchange_name' routing_key = '' - yield from self.channel.queue_declare(queue_name) - yield from self.channel.exchange_declare(exchange_name, type_name='direct') - yield from self.channel.queue_bind(queue_name, exchange_name, routing_key=routing_key) + await self.channel.queue_declare(queue_name) + await self.channel.exchange_declare(exchange_name, type_name='direct') + await self.channel.queue_bind(queue_name, exchange_name, routing_key=routing_key) - yield from self.channel.publish("payload", exchange_name, routing_key=routing_key) + await self.channel.publish("payload", exchange_name, routing_key=routing_key) - result = yield from self.channel.basic_get(queue_name) + result = await self.channel.basic_get(queue_name) self.assertEqual(result['routing_key'], routing_key) self.assertFalse(result['redelivered']) self.assertIn('delivery_tag', result) @@ -102,138 +94,126 @@ def test_basic_get(self): self.assertEqual(result['message'], b'payload') self.assertIsInstance(result['properties'], properties.Properties) - @testing.coroutine - def test_basic_get_empty(self): + async def test_basic_get_empty(self): queue_name = 'queue_name' exchange_name = 'exchange_name' routing_key = '' - yield from self.channel.queue_declare(queue_name) - yield from self.channel.exchange_declare(exchange_name, type_name='direct') - yield from self.channel.queue_bind(queue_name, exchange_name, routing_key=routing_key) + await self.channel.queue_declare(queue_name) + await self.channel.exchange_declare(exchange_name, type_name='direct') + await self.channel.queue_bind(queue_name, exchange_name, routing_key=routing_key) with self.assertRaises(exceptions.EmptyQueue): - yield from self.channel.basic_get(queue_name) + await self.channel.basic_get(queue_name) class BasicDeliveryTestCase(testcase.RabbitTestCase, unittest.TestCase): - @asyncio.coroutine - def publish(self, queue_name, exchange_name, routing_key, payload): - yield from self.channel.queue_declare(queue_name, exclusive=False, no_wait=False) - yield from self.channel.exchange_declare(exchange_name, type_name='fanout') - yield from self.channel.queue_bind(queue_name, exchange_name, routing_key=routing_key) - yield from self.channel.publish(payload, exchange_name, queue_name) + async def publish(self, queue_name, exchange_name, routing_key, payload): + await self.channel.queue_declare(queue_name, exclusive=False, no_wait=False) + await self.channel.exchange_declare(exchange_name, type_name='fanout') + await self.channel.queue_bind(queue_name, exchange_name, routing_key=routing_key) + await self.channel.publish(payload, exchange_name, queue_name) - @testing.coroutine - def test_ack_message(self): + async def test_ack_message(self): queue_name = 'queue_name' exchange_name = 'exchange_name' routing_key = '' - yield from self.publish( + await self.publish( queue_name, exchange_name, routing_key, "payload" ) qfuture = asyncio.Future(loop=self.loop) - @asyncio.coroutine - def qcallback(channel, body, envelope, _properties): + async def qcallback(channel, body, envelope, _properties): qfuture.set_result(envelope) - yield from self.channel.basic_consume(qcallback, queue_name=queue_name) - envelope = yield from qfuture + await self.channel.basic_consume(qcallback, queue_name=queue_name) + envelope = await qfuture - yield from qfuture - yield from self.channel.basic_client_ack(envelope.delivery_tag) + await qfuture + await self.channel.basic_client_ack(envelope.delivery_tag) - @testing.coroutine - def test_basic_nack(self): + async def test_basic_nack(self): queue_name = 'queue_name' exchange_name = 'exchange_name' routing_key = '' - yield from self.publish( + await self.publish( queue_name, exchange_name, routing_key, "payload" ) qfuture = asyncio.Future(loop=self.loop) - @asyncio.coroutine - def qcallback(channel, body, envelope, _properties): - yield from self.channel.basic_client_nack( + async def qcallback(channel, body, envelope, _properties): + await self.channel.basic_client_nack( envelope.delivery_tag, multiple=True, requeue=False ) qfuture.set_result(True) - yield from self.channel.basic_consume(qcallback, queue_name=queue_name) - yield from qfuture + await self.channel.basic_consume(qcallback, queue_name=queue_name) + await qfuture - @testing.coroutine - def test_basic_nack_norequeue(self): + async def test_basic_nack_norequeue(self): queue_name = 'queue_name' exchange_name = 'exchange_name' routing_key = '' - yield from self.publish( + await self.publish( queue_name, exchange_name, routing_key, "payload" ) qfuture = asyncio.Future(loop=self.loop) - @asyncio.coroutine - def qcallback(channel, body, envelope, _properties): - yield from self.channel.basic_client_nack(envelope.delivery_tag, requeue=False) + async def qcallback(channel, body, envelope, _properties): + await self.channel.basic_client_nack(envelope.delivery_tag, requeue=False) qfuture.set_result(True) - yield from self.channel.basic_consume(qcallback, queue_name=queue_name) - yield from qfuture + await self.channel.basic_consume(qcallback, queue_name=queue_name) + await qfuture - @testing.coroutine - def test_basic_nack_requeue(self): + async def test_basic_nack_requeue(self): queue_name = 'queue_name' exchange_name = 'exchange_name' routing_key = '' - yield from self.publish( + await self.publish( queue_name, exchange_name, routing_key, "payload" ) qfuture = asyncio.Future(loop=self.loop) called = False - @asyncio.coroutine - def qcallback(channel, body, envelope, _properties): + async def qcallback(channel, body, envelope, _properties): nonlocal called if not called: called = True - yield from self.channel.basic_client_nack(envelope.delivery_tag, requeue=True) + await self.channel.basic_client_nack(envelope.delivery_tag, requeue=True) else: - yield from self.channel.basic_client_ack(envelope.delivery_tag) + await self.channel.basic_client_ack(envelope.delivery_tag) qfuture.set_result(True) - yield from self.channel.basic_consume(qcallback, queue_name=queue_name) - yield from qfuture + await self.channel.basic_consume(qcallback, queue_name=queue_name) + await qfuture - @testing.coroutine - def test_basic_reject(self): + async def test_basic_reject(self): queue_name = 'queue_name' exchange_name = 'exchange_name' routing_key = '' - yield from self.publish( + await self.publish( queue_name, exchange_name, routing_key, "payload" ) qfuture = asyncio.Future(loop=self.loop) - @asyncio.coroutine - def qcallback(channel, body, envelope, _properties): + async def qcallback(channel, body, envelope, _properties): qfuture.set_result(envelope) - yield from self.channel.basic_consume(qcallback, queue_name=queue_name) - envelope = yield from qfuture + await self.channel.basic_consume(qcallback, queue_name=queue_name) + envelope = await qfuture - yield from self.channel.basic_reject(envelope.delivery_tag) + await self.channel.basic_reject(envelope.delivery_tag) diff --git a/aioamqp/tests/test_channel.py b/aioamqp/tests/test_channel.py index 06e91df..ee57448 100644 --- a/aioamqp/tests/test_channel.py +++ b/aioamqp/tests/test_channel.py @@ -15,83 +15,73 @@ class ChannelTestCase(testcase.RabbitTestCase, unittest.TestCase): _multiprocess_can_split_ = True - @testing.coroutine - def test_open(self): - channel = yield from self.amqp.channel() + async def test_open(self): + channel = await self.amqp.channel() self.assertNotEqual(channel.channel_id, 0) self.assertTrue(channel.is_open) - @testing.coroutine - def test_close(self): - channel = yield from self.amqp.channel() - result = yield from channel.close() + async def test_close(self): + channel = await self.amqp.channel() + result = await channel.close() self.assertEqual(result, True) self.assertFalse(channel.is_open) - @testing.coroutine - def test_server_initiated_close(self): - channel = yield from self.amqp.channel() + async def test_server_initiated_close(self): + channel = await self.amqp.channel() try: - yield from channel.basic_get(queue_name='non-existant') + await channel.basic_get(queue_name='non-existant') except exceptions.ChannelClosed as e: self.assertEqual(e.code, 404) self.assertFalse(channel.is_open) - channel = yield from self.amqp.channel() + channel = await self.amqp.channel() - @testing.coroutine - def test_alreadyclosed_channel(self): - channel = yield from self.amqp.channel() - result = yield from channel.close() + async def test_alreadyclosed_channel(self): + channel = await self.amqp.channel() + result = await channel.close() self.assertEqual(result, True) with self.assertRaises(exceptions.ChannelClosed): - result = yield from channel.close() + result = await channel.close() - @testing.coroutine - def test_multiple_open(self): - channel1 = yield from self.amqp.channel() - channel2 = yield from self.amqp.channel() + async def test_multiple_open(self): + channel1 = await self.amqp.channel() + channel2 = await self.amqp.channel() self.assertNotEqual(channel1.channel_id, channel2.channel_id) - @testing.coroutine - def test_channel_active_flow(self): - channel = yield from self.amqp.channel() - result = yield from channel.flow(active=True) + async def test_channel_active_flow(self): + channel = await self.amqp.channel() + result = await channel.flow(active=True) self.assertTrue(result['active']) - @testing.coroutine @unittest.skipIf(IMPLEMENT_CHANNEL_FLOW is False, "active=false is not implemented in RabbitMQ") - def test_channel_inactive_flow(self): - channel = yield from self.amqp.channel() - result = yield from channel.flow(active=False) + async def test_channel_inactive_flow(self): + channel = await self.amqp.channel() + result = await channel.flow(active=False) self.assertFalse(result['active']) - result = yield from channel.flow(active=True) + result = await channel.flow(active=True) - @testing.coroutine - def test_channel_active_flow_twice(self): - channel = yield from self.amqp.channel() - result = yield from channel.flow(active=True) + async def test_channel_active_flow_twice(self): + channel = await self.amqp.channel() + result = await channel.flow(active=True) self.assertTrue(result['active']) - result = yield from channel.flow(active=True) + result = await channel.flow(active=True) - @testing.coroutine @unittest.skipIf(IMPLEMENT_CHANNEL_FLOW is False, "active=false is not implemented in RabbitMQ") - def test_channel_active_inactive_flow(self): - channel = yield from self.amqp.channel() - result = yield from channel.flow(active=True) + async def test_channel_active_inactive_flow(self): + channel = await self.amqp.channel() + result = await channel.flow(active=True) self.assertTrue(result['active']) - result = yield from channel.flow(active=False) + result = await channel.flow(active=False) self.assertFalse(result['active']) class ChannelIdTestCase(testcase.RabbitTestCase, unittest.TestCase): - @testing.coroutine - def test_channel_id_release_close(self): + async def test_channel_id_release_close(self): channels_count_start = self.amqp.channels_ids_count - channel = yield from self.amqp.channel() + channel = await self.amqp.channel() self.assertEqual(self.amqp.channels_ids_count, channels_count_start + 1) - result = yield from channel.close() + result = await channel.close() self.assertEqual(result, True) self.assertFalse(channel.is_open) self.assertEqual(self.amqp.channels_ids_count, channels_count_start) diff --git a/aioamqp/tests/test_close.py b/aioamqp/tests/test_close.py index c5e1824..6ab807a 100644 --- a/aioamqp/tests/test_close.py +++ b/aioamqp/tests/test_close.py @@ -12,50 +12,43 @@ def setUp(self): super().setUp() self.consume_future = asyncio.Future(loop=self.loop) - @asyncio.coroutine - def callback(self, body, envelope, properties): + async def callback(self, body, envelope, properties): self.consume_future.set_result((body, envelope, properties)) - @asyncio.coroutine - def get_callback_result(self): - yield from self.consume_future + async def get_callback_result(self): + await self.consume_future result = self.consume_future.result() self.consume_future = asyncio.Future(loop=self.loop) return result - @testing.coroutine - def test_close(self): - channel = yield from self.create_channel() + async def test_close(self): + channel = await self.create_channel() self.assertTrue(channel.is_open) - yield from channel.close() + await channel.close() self.assertFalse(channel.is_open) - @testing.coroutine - def test_multiple_close(self): - channel = yield from self.create_channel() - yield from channel.close() + async def test_multiple_close(self): + channel = await self.create_channel() + await channel.close() self.assertFalse(channel.is_open) with self.assertRaises(exceptions.ChannelClosed): - yield from channel.close() + await channel.close() - @testing.coroutine - def test_cannot_publish_after_close(self): + async def test_cannot_publish_after_close(self): channel = self.channel - yield from channel.close() + await channel.close() with self.assertRaises(exceptions.ChannelClosed): - yield from self.channel.publish("coucou", "my_e", "") + await self.channel.publish("coucou", "my_e", "") - @testing.coroutine - def test_cannot_declare_queue_after_close(self): + async def test_cannot_declare_queue_after_close(self): channel = self.channel - yield from channel.close() + await channel.close() with self.assertRaises(exceptions.ChannelClosed): - yield from self.channel.queue_declare("qq") + await self.channel.queue_declare("qq") - @testing.coroutine - def test_cannot_consume_after_close(self): + async def test_cannot_consume_after_close(self): channel = self.channel - yield from self.channel.queue_declare("q") - yield from channel.close() + await self.channel.queue_declare("q") + await channel.close() with self.assertRaises(exceptions.ChannelClosed): - yield from channel.basic_consume(self.callback) + await channel.basic_consume(self.callback) diff --git a/aioamqp/tests/test_connect.py b/aioamqp/tests/test_connect.py index 9ec122f..93bd5d7 100644 --- a/aioamqp/tests/test_connect.py +++ b/aioamqp/tests/test_connect.py @@ -11,20 +11,18 @@ class AmqpConnectionTestCase(testcase.RabbitTestCase, unittest.TestCase): - @testing.coroutine - def test_connect(self): - _transport, proto = yield from connect(host=self.host, port=self.port, virtualhost=self.vhost, loop=self.loop) + async def test_connect(self): + _transport, proto = await connect(host=self.host, port=self.port, virtualhost=self.vhost, loop=self.loop) self.assertEqual(proto.state, OPEN) self.assertIsNotNone(proto.server_properties) - yield from proto.close() + await proto.close() - @testing.coroutine - def test_connect_tuning(self): + async def test_connect_tuning(self): # frame_max should be higher than 131072 frame_max = 131072 channel_max = 10 heartbeat = 100 - _transport, proto = yield from connect( + _transport, proto = await connect( host=self.host, port=self.port, virtualhost=self.vhost, @@ -46,12 +44,11 @@ def test_connect_tuning(self): self.assertEqual(proto.server_frame_max, frame_max) self.assertEqual(proto.server_heartbeat, heartbeat) - yield from proto.close() + await proto.close() - @testing.coroutine - def test_socket_nodelay(self): - transport, proto = yield from connect(host=self.host, port=self.port, virtualhost=self.vhost, loop=self.loop) + async def test_socket_nodelay(self): + transport, proto = await connect(host=self.host, port=self.port, virtualhost=self.vhost, loop=self.loop) sock = transport.get_extra_info('socket') opt_val = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) self.assertNotEqual(opt_val, 0) - yield from proto.close() + await proto.close() diff --git a/aioamqp/tests/test_connection_close.py b/aioamqp/tests/test_connection_close.py index b211a87..4c64dd1 100644 --- a/aioamqp/tests/test_connection_close.py +++ b/aioamqp/tests/test_connection_close.py @@ -9,14 +9,13 @@ class CloseTestCase(testcase.RabbitTestCase, unittest.TestCase): - @testing.coroutine - def test_close(self): + async def test_close(self): amqp = self.amqp self.assertEqual(amqp.state, OPEN) # grab a ref here because py36 sets _stream_reader to None in # StreamReaderProtocol.connection_lost() transport = amqp._stream_reader._transport - yield from amqp.close() + await amqp.close() self.assertEqual(amqp.state, CLOSED) if hasattr(transport, 'is_closing'): self.assertTrue(transport.is_closing()) @@ -24,13 +23,12 @@ def test_close(self): # TODO: remove with python <3.4.4 support self.assertTrue(transport._closing) # make sure those 2 tasks/futures are properly set as finished - yield from amqp.stop_now - yield from amqp.worker + await amqp.stop_now + await amqp.worker - @testing.coroutine - def test_multiple_close(self): + async def test_multiple_close(self): amqp = self.amqp - yield from amqp.close() + await amqp.close() self.assertEqual(amqp.state, CLOSED) with self.assertRaises(AmqpClosedConnection): - yield from amqp.close() + await amqp.close() diff --git a/aioamqp/tests/test_connection_lost.py b/aioamqp/tests/test_connection_lost.py index f339c55..adad88b 100644 --- a/aioamqp/tests/test_connection_lost.py +++ b/aioamqp/tests/test_connection_lost.py @@ -12,8 +12,7 @@ class ConnectionLostTestCase(testcase.RabbitTestCase, unittest.TestCase): _multiprocess_can_split_ = True - @testing.coroutine - def test_connection_lost(self): + async def test_connection_lost(self): self.callback_called = False @@ -26,7 +25,7 @@ def callback(*args, **kwargs): self.assertEqual(amqp.state, OPEN) self.assertTrue(channel.is_open) amqp._stream_reader._transport.close() # this should have the same effect as the tcp connection being lost - yield from asyncio.wait_for(amqp.worker, 1, loop=self.loop) + await asyncio.wait_for(amqp.worker, 1, loop=self.loop) self.assertEqual(amqp.state, CLOSED) self.assertFalse(channel.is_open) self.assertTrue(self.callback_called) diff --git a/aioamqp/tests/test_consume.py b/aioamqp/tests/test_consume.py index e0a7d1a..b313ac0 100644 --- a/aioamqp/tests/test_consume.py +++ b/aioamqp/tests/test_consume.py @@ -17,167 +17,156 @@ def setUp(self): super().setUp() self.consume_future = asyncio.Future(loop=self.loop) - @asyncio.coroutine - def callback(self, channel, body, envelope, properties): + async def callback(self, channel, body, envelope, properties): self.consume_future.set_result((body, envelope, properties)) - @asyncio.coroutine - def get_callback_result(self): - yield from self.consume_future + async def get_callback_result(self): + await self.consume_future result = self.consume_future.result() self.consume_future = asyncio.Future(loop=self.loop) return result - - def test_wrong_callback_argument(self): + async def test_wrong_callback_argument(self): def badcallback(): pass - yield from self.channel.queue_declare("q", exclusive=True, no_wait=False) - yield from self.channel.exchange_declare("e", "fanout") - yield from self.channel.queue_bind("q", "e", routing_key='') + await self.channel.queue_declare("q", exclusive=True, no_wait=False) + await self.channel.exchange_declare("e", "fanout") + await self.channel.queue_bind("q", "e", routing_key='') # get a different channel - channel = yield from self.create_channel() + channel = await self.create_channel() # publish - yield from channel.publish("coucou", "e", routing_key='',) + await channel.publish("coucou", "e", routing_key='',) # assert there is a message to consume queues = self.list_queues() self.assertIn("q", queues) self.assertEqual(1, queues["q"]['messages']) - yield from asyncio.sleep(2, loop=self.loop) + await asyncio.sleep(2, loop=self.loop) # start consume with self.assertRaises(exceptions.ConfigurationError): - yield from channel.basic_consume(badcallback, queue_name="q") + await channel.basic_consume(badcallback, queue_name="q") - @testing.coroutine - def test_consume(self): + async def test_consume(self): # declare - yield from self.channel.queue_declare("q", exclusive=True, no_wait=False) - yield from self.channel.exchange_declare("e", "fanout") - yield from self.channel.queue_bind("q", "e", routing_key='') + await self.channel.queue_declare("q", exclusive=True, no_wait=False) + await self.channel.exchange_declare("e", "fanout") + await self.channel.queue_bind("q", "e", routing_key='') # get a different channel - channel = yield from self.create_channel() + channel = await self.create_channel() # publish - yield from channel.publish("coucou", "e", routing_key='',) + await channel.publish("coucou", "e", routing_key='',) # start consume - yield from channel.basic_consume(self.callback, queue_name="q") + await channel.basic_consume(self.callback, queue_name="q") # get one - body, envelope, properties = yield from self.get_callback_result() + body, envelope, properties = await self.get_callback_result() self.assertIsNotNone(envelope.consumer_tag) self.assertIsNotNone(envelope.delivery_tag) self.assertEqual(b"coucou", body) self.assertIsInstance(properties, Properties) - @testing.coroutine - def test_big_consume(self): + async def test_big_consume(self): # declare - yield from self.channel.queue_declare("q", exclusive=True, no_wait=False) - yield from self.channel.exchange_declare("e", "fanout") - yield from self.channel.queue_bind("q", "e", routing_key='') + await self.channel.queue_declare("q", exclusive=True, no_wait=False) + await self.channel.exchange_declare("e", "fanout") + await self.channel.queue_bind("q", "e", routing_key='') # get a different channel - channel = yield from self.create_channel() + channel = await self.create_channel() # publish - yield from channel.publish("a"*1000000, "e", routing_key='',) + await channel.publish("a"*1000000, "e", routing_key='',) # start consume - yield from channel.basic_consume(self.callback, queue_name="q") + await channel.basic_consume(self.callback, queue_name="q") # get one - body, envelope, properties = yield from self.get_callback_result() + body, envelope, properties = await self.get_callback_result() self.assertIsNotNone(envelope.consumer_tag) self.assertIsNotNone(envelope.delivery_tag) self.assertEqual(b"a"*1000000, body) self.assertIsInstance(properties, Properties) - @testing.coroutine - def test_consume_multiple_queues(self): - yield from self.channel.queue_declare("q1", exclusive=True, no_wait=False) - yield from self.channel.queue_declare("q2", exclusive=True, no_wait=False) - yield from self.channel.exchange_declare("e", "direct") - yield from self.channel.queue_bind("q1", "e", routing_key="q1") - yield from self.channel.queue_bind("q2", "e", routing_key="q2") + async def test_consume_multiple_queues(self): + await self.channel.queue_declare("q1", exclusive=True, no_wait=False) + await self.channel.queue_declare("q2", exclusive=True, no_wait=False) + await self.channel.exchange_declare("e", "direct") + await self.channel.queue_bind("q1", "e", routing_key="q1") + await self.channel.queue_bind("q2", "e", routing_key="q2") # get a different channel - channel = yield from self.create_channel() + channel = await self.create_channel() q1_future = asyncio.Future(loop=self.loop) - @asyncio.coroutine - def q1_callback(channel, body, envelope, properties): + async def q1_callback(channel, body, envelope, properties): q1_future.set_result((body, envelope, properties)) q2_future = asyncio.Future(loop=self.loop) - @asyncio.coroutine - def q2_callback(channel, body, envelope, properties): + async def q2_callback(channel, body, envelope, properties): q2_future.set_result((body, envelope, properties)) # start consumers - result = yield from channel.basic_consume(q1_callback, queue_name="q1") + result = await channel.basic_consume(q1_callback, queue_name="q1") ctag_q1 = result['consumer_tag'] - result = yield from channel.basic_consume(q2_callback, queue_name="q2") + result = await channel.basic_consume(q2_callback, queue_name="q2") ctag_q2 = result['consumer_tag'] # put message in q1 - yield from channel.publish("coucou1", "e", "q1") + await channel.publish("coucou1", "e", "q1") # get it - body1, envelope1, properties1 = yield from q1_future + body1, envelope1, properties1 = await q1_future self.assertEqual(ctag_q1, envelope1.consumer_tag) self.assertIsNotNone(envelope1.delivery_tag) self.assertEqual(b"coucou1", body1) self.assertIsInstance(properties1, Properties) # put message in q2 - yield from channel.publish("coucou2", "e", "q2") + await channel.publish("coucou2", "e", "q2") # get it - body2, envelope2, properties2 = yield from q2_future + body2, envelope2, properties2 = await q2_future self.assertEqual(ctag_q2, envelope2.consumer_tag) self.assertEqual(b"coucou2", body2) self.assertIsInstance(properties2, Properties) - @testing.coroutine - def test_duplicate_consumer_tag(self): - yield from self.channel.queue_declare("q1", exclusive=True, no_wait=False) - yield from self.channel.queue_declare("q2", exclusive=True, no_wait=False) - yield from self.channel.basic_consume(self.callback, queue_name="q1", consumer_tag='tag') + async def test_duplicate_consumer_tag(self): + await self.channel.queue_declare("q1", exclusive=True, no_wait=False) + await self.channel.queue_declare("q2", exclusive=True, no_wait=False) + await self.channel.basic_consume(self.callback, queue_name="q1", consumer_tag='tag') with self.assertRaises(exceptions.ChannelClosed) as cm: - yield from self.channel.basic_consume(self.callback, queue_name="q2", consumer_tag='tag') + await self.channel.basic_consume(self.callback, queue_name="q2", consumer_tag='tag') self.assertEqual(cm.exception.code, 530) - @testing.coroutine - def test_consume_callaback_synced(self): + async def test_consume_callaback_synced(self): # declare - yield from self.channel.queue_declare("q", exclusive=True, no_wait=False) - yield from self.channel.exchange_declare("e", "fanout") - yield from self.channel.queue_bind("q", "e", routing_key='') + await self.channel.queue_declare("q", exclusive=True, no_wait=False) + await self.channel.exchange_declare("e", "fanout") + await self.channel.queue_bind("q", "e", routing_key='') # get a different channel - channel = yield from self.create_channel() + channel = await self.create_channel() # publish - yield from channel.publish("coucou", "e", routing_key='',) + await channel.publish("coucou", "e", routing_key='',) sync_future = asyncio.Future(loop=self.loop) - @asyncio.coroutine - def callback(channel, body, envelope, properties): + async def callback(channel, body, envelope, properties): self.assertTrue(sync_future.done()) - yield from channel.basic_consume(callback, queue_name="q") + await channel.basic_consume(callback, queue_name="q") sync_future.set_result(True) diff --git a/aioamqp/tests/test_exchange.py b/aioamqp/tests/test_exchange.py index abdeb75..cf1a684 100644 --- a/aioamqp/tests/test_exchange.py +++ b/aioamqp/tests/test_exchange.py @@ -14,72 +14,64 @@ class ExchangeDeclareTestCase(testcase.RabbitTestCase, unittest.TestCase): _multiprocess_can_split_ = True - @testing.coroutine - def test_exchange_direct_declare(self): - result = yield from self.channel.exchange_declare( + async def test_exchange_direct_declare(self): + result = await self.channel.exchange_declare( 'exchange_name', type_name='direct') self.assertTrue(result) - @testing.coroutine - def test_exchange_fanout_declare(self): - result = yield from self.channel.exchange_declare( + async def test_exchange_fanout_declare(self): + result = await self.channel.exchange_declare( 'exchange_name', type_name='fanout') self.assertTrue(result) - @testing.coroutine - def test_exchange_topic_declare(self): - result = yield from self.channel.exchange_declare( + async def test_exchange_topic_declare(self): + result = await self.channel.exchange_declare( 'exchange_name', type_name='topic') self.assertTrue(result) - @testing.coroutine - def test_exchange_headers_declare(self): - result = yield from self.channel.exchange_declare( + async def test_exchange_headers_declare(self): + result = await self.channel.exchange_declare( 'exchange_name', type_name='headers') self.assertTrue(result) - @testing.coroutine - def test_exchange_declare_wrong_types(self): - result = yield from self.channel.exchange_declare( + async def test_exchange_declare_wrong_types(self): + result = await self.channel.exchange_declare( 'exchange_name', type_name='headers', auto_delete=True, durable=True) self.assertTrue(result) with self.assertRaises(exceptions.ChannelClosed): - result = yield from self.channel.exchange_declare( + result = await self.channel.exchange_declare( 'exchange_name', type_name='fanout', auto_delete=False, durable=False) - @testing.coroutine - def test_exchange_declare_passive(self): - result = yield from self.channel.exchange_declare( + async def test_exchange_declare_passive(self): + result = await self.channel.exchange_declare( 'exchange_name', type_name='headers', auto_delete=True, durable=True) self.assertTrue(result) - result = yield from self.channel.exchange_declare( + result = await self.channel.exchange_declare( 'exchange_name', type_name='headers', auto_delete=True, durable=True, passive=True) self.assertTrue(result) - result = yield from self.channel.exchange_declare( + result = await self.channel.exchange_declare( 'exchange_name', type_name='headers', auto_delete=False, durable=False, passive=True) self.assertTrue(result) - @testing.coroutine - def test_exchange_declare_passive_does_not_exists(self): + async def test_exchange_declare_passive_does_not_exists(self): with self.assertRaises(exceptions.ChannelClosed) as cm: - yield from self.channel.exchange_declare( + await self.channel.exchange_declare( 'non_existant_exchange', type_name='headers', auto_delete=False, durable=False, passive=True) self.assertEqual(cm.exception.code, 404) - @asyncio.coroutine - def test_exchange_declare_unknown_type(self): + async def test_exchange_declare_unknown_type(self): with self.assertRaises(exceptions.ChannelClosed): - yield from self.channel.exchange_declare( + await self.channel.exchange_declare( 'non_existant_exchange', type_name='unknown_type', auto_delete=False, durable=False, passive=True) @@ -87,53 +79,49 @@ def test_exchange_declare_unknown_type(self): class ExchangeDelete(testcase.RabbitTestCase, unittest.TestCase): - @testing.coroutine - def test_delete(self): + async def test_delete(self): exchange_name = 'exchange_name' - yield from self.channel.exchange_declare(exchange_name, type_name='direct') - result = yield from self.channel.exchange_delete(exchange_name) + await self.channel.exchange_declare(exchange_name, type_name='direct') + result = await self.channel.exchange_delete(exchange_name) self.assertTrue(result) with self.assertRaises(exceptions.ChannelClosed) as cm: - yield from self.channel.exchange_declare( + await self.channel.exchange_declare( exchange_name, type_name='direct', passive=True ) self.assertEqual(cm.exception.code, 404) - @testing.coroutine - def test_double_delete(self): + async def test_double_delete(self): exchange_name = 'exchange_name' - yield from self.channel.exchange_declare(exchange_name, type_name='direct') - result = yield from self.channel.exchange_delete(exchange_name) + await self.channel.exchange_declare(exchange_name, type_name='direct') + result = await self.channel.exchange_delete(exchange_name) self.assertTrue(result) if self.server_version() < (3, 3, 5): with self.assertRaises(exceptions.ChannelClosed) as cm: - yield from self.channel.exchange_delete(exchange_name) + await self.channel.exchange_delete(exchange_name) self.assertEqual(cm.exception.code, 404) else: # weird result from rabbitmq 3.3.5 - result = yield from self.channel.exchange_delete(exchange_name) + result = await self.channel.exchange_delete(exchange_name) self.assertTrue(result) class ExchangeBind(testcase.RabbitTestCase, unittest.TestCase): - @testing.coroutine - def test_exchange_bind(self): - yield from self.channel.exchange_declare('exchange_destination', type_name='direct') - yield from self.channel.exchange_declare('exchange_source', type_name='direct') + async def test_exchange_bind(self): + await self.channel.exchange_declare('exchange_destination', type_name='direct') + await self.channel.exchange_declare('exchange_source', type_name='direct') - result = yield from self.channel.exchange_bind( + result = await self.channel.exchange_bind( 'exchange_destination', 'exchange_source', routing_key='') self.assertTrue(result) - @testing.coroutine - def test_inexistant_exchange_bind(self): + async def test_inexistant_exchange_bind(self): with self.assertRaises(exceptions.ChannelClosed) as cm: - yield from self.channel.exchange_bind( + await self.channel.exchange_bind( 'exchange_destination', 'exchange_source', routing_key='') self.assertEqual(cm.exception.code, 404) @@ -142,37 +130,35 @@ def test_inexistant_exchange_bind(self): class ExchangeUnbind(testcase.RabbitTestCase, unittest.TestCase): - @testing.coroutine - def test_exchange_unbind(self): + async def test_exchange_unbind(self): ex_source = 'exchange_source' ex_destination = 'exchange_destination' - yield from self.channel.exchange_declare(ex_destination, type_name='direct') - yield from self.channel.exchange_declare(ex_source, type_name='direct') + await self.channel.exchange_declare(ex_destination, type_name='direct') + await self.channel.exchange_declare(ex_source, type_name='direct') - yield from self.channel.exchange_bind( + await self.channel.exchange_bind( ex_destination, ex_source, routing_key='') - yield from self.channel.exchange_unbind( + await self.channel.exchange_unbind( ex_destination, ex_source, routing_key='') - @testing.coroutine - def test_exchange_unbind_reversed(self): + async def test_exchange_unbind_reversed(self): ex_source = 'exchange_source' ex_destination = 'exchange_destination' - yield from self.channel.exchange_declare(ex_destination, type_name='direct') - yield from self.channel.exchange_declare(ex_source, type_name='direct') + await self.channel.exchange_declare(ex_destination, type_name='direct') + await self.channel.exchange_declare(ex_source, type_name='direct') - yield from self.channel.exchange_bind( + await self.channel.exchange_bind( ex_destination, ex_source, routing_key='') if self.server_version() < (3, 3, 5): with self.assertRaises(exceptions.ChannelClosed) as cm: - result = yield from self.channel.exchange_unbind( + result = await self.channel.exchange_unbind( ex_source, ex_destination, routing_key='') self.assertEqual(cm.exception.code, 404) else: # weird result from rabbitmq 3.3.5 - result = yield from self.channel.exchange_unbind(ex_source, ex_destination, routing_key='') + result = await self.channel.exchange_unbind(ex_source, ex_destination, routing_key='') self.assertTrue(result) diff --git a/aioamqp/tests/test_heartbeat.py b/aioamqp/tests/test_heartbeat.py index 6dd95eb..fe32a17 100644 --- a/aioamqp/tests/test_heartbeat.py +++ b/aioamqp/tests/test_heartbeat.py @@ -14,8 +14,7 @@ class HeartbeatTestCase(testcase.RabbitTestCase, unittest.TestCase): - @testing.coroutine - def test_heartbeat(self): + async def test_heartbeat(self): with mock.patch.object( self.amqp, 'send_heartbeat', wraps=self.amqp.send_heartbeat ) as send_heartbeat: @@ -25,8 +24,8 @@ def test_heartbeat(self): self.amqp._heartbeat_timer_send_reset() self.amqp._heartbeat_timer_recv_reset() - yield from asyncio.sleep(1.001) + await asyncio.sleep(1.001) send_heartbeat.assert_called_once_with() - yield from asyncio.sleep(1.001) + await asyncio.sleep(1.001) self.assertEqual(self.amqp.state, CLOSED) diff --git a/aioamqp/tests/test_properties.py b/aioamqp/tests/test_properties.py index 206b0bd..e4e611d 100644 --- a/aioamqp/tests/test_properties.py +++ b/aioamqp/tests/test_properties.py @@ -14,77 +14,76 @@ class ReplyTestCase(testcase.RabbitTestCase, unittest.TestCase): - @asyncio.coroutine - def _server(self, server_future, exchange_name, routing_key): + + async def _server(self, server_future, exchange_name, routing_key): """Consume messages and reply to them by publishing messages back to the client using routing key set to the reply_to property""" server_queue_name = 'server_queue' - yield from self.channel.queue_declare(server_queue_name, exclusive=False, no_wait=False) - yield from self.channel.exchange_declare(exchange_name, type_name='direct') - yield from self.channel.queue_bind( + await self.channel.queue_declare(server_queue_name, exclusive=False, no_wait=False) + await self.channel.exchange_declare(exchange_name, type_name='direct') + await self.channel.queue_bind( server_queue_name, exchange_name, routing_key=routing_key) - @asyncio.coroutine - def server_callback(channel, body, envelope, properties): + + async def server_callback(channel, body, envelope, properties): logger.debug('Server received message') server_future.set_result((body, envelope, properties)) publish_properties = {'correlation_id': properties.correlation_id} logger.debug('Replying to %r', properties.reply_to) - yield from self.channel.publish( + await self.channel.publish( b'reply message', exchange_name, properties.reply_to, publish_properties) logger.debug('Server replied') - yield from self.channel.basic_consume(server_callback, queue_name=server_queue_name) + await self.channel.basic_consume(server_callback, queue_name=server_queue_name) logger.debug('Server consuming messages') - @asyncio.coroutine - def _client( + + async def _client( self, client_future, exchange_name, server_routing_key, correlation_id, client_routing_key): """Declare a queue, bind client_routing_key to it and publish a message to the server with the reply_to property set to that routing key""" client_queue_name = 'client_reply_queue' - client_channel = yield from self.create_channel() - yield from client_channel.queue_declare( + client_channel = await self.create_channel() + await client_channel.queue_declare( client_queue_name, exclusive=True, no_wait=False) - yield from client_channel.queue_bind( + await client_channel.queue_bind( client_queue_name, exchange_name, routing_key=client_routing_key) - @asyncio.coroutine - def client_callback(channel, body, envelope, properties): + + async def client_callback(channel, body, envelope, properties): logger.debug('Client received message') client_future.set_result((body, envelope, properties)) - yield from client_channel.basic_consume(client_callback, queue_name=client_queue_name) + await client_channel.basic_consume(client_callback, queue_name=client_queue_name) logger.debug('Client consuming messages') - yield from client_channel.publish( + await client_channel.publish( b'client message', exchange_name, server_routing_key, {'correlation_id': correlation_id, 'reply_to': client_routing_key}) logger.debug('Client published message') - @testing.coroutine - def test_reply_to(self): + async def test_reply_to(self): exchange_name = 'exchange_name' server_routing_key = 'reply_test' server_future = asyncio.Future(loop=self.loop) - yield from self._server(server_future, exchange_name, server_routing_key) + await self._server(server_future, exchange_name, server_routing_key) correlation_id = 'secret correlation id' client_routing_key = 'secret_client_key' client_future = asyncio.Future(loop=self.loop) - yield from self._client( + await self._client( client_future, exchange_name, server_routing_key, correlation_id, client_routing_key) logger.debug('Waiting for server to receive message') - server_body, server_envelope, server_properties = yield from server_future + server_body, server_envelope, server_properties = await server_future self.assertEqual(server_body, b'client message') self.assertEqual(server_properties.correlation_id, correlation_id) self.assertEqual(server_properties.reply_to, client_routing_key) self.assertEqual(server_envelope.routing_key, server_routing_key) logger.debug('Waiting for client to receive message') - client_body, client_envelope, client_properties = yield from client_future + client_body, client_envelope, client_properties = await client_future self.assertEqual(client_body, b'reply message') self.assertEqual(client_properties.correlation_id, correlation_id) self.assertEqual(client_envelope.routing_key, client_routing_key) diff --git a/aioamqp/tests/test_protocol.py b/aioamqp/tests/test_protocol.py index c0e1ec4..92c0933 100644 --- a/aioamqp/tests/test_protocol.py +++ b/aioamqp/tests/test_protocol.py @@ -17,19 +17,17 @@ class ProtocolTestCase(testcase.RabbitTestCase, unittest.TestCase): - @testing.coroutine - def test_connect(self): - _transport, protocol = yield from amqp_connect(host=self.host, port=self.port, virtualhost=self.vhost, loop=self.loop) + async def test_connect(self): + _transport, protocol = await amqp_connect(host=self.host, port=self.port, virtualhost=self.vhost, loop=self.loop) self.assertEqual(protocol.state, OPEN) - yield from protocol.close() + await protocol.close() - @testing.coroutine - def test_connect_products_info(self): + async def test_connect_products_info(self): client_properties = { 'program': 'aioamqp-tests', 'program_version': '0.1.1', } - _transport, protocol = yield from amqp_connect( + _transport, protocol = await amqp_connect( host=self.host, port=self.port, virtualhost=self.vhost, @@ -38,25 +36,22 @@ def test_connect_products_info(self): ) self.assertEqual(protocol.client_properties, client_properties) - yield from protocol.close() + await protocol.close() - @testing.coroutine - def test_connection_unexistant_vhost(self): + async def test_connection_unexistant_vhost(self): with self.assertRaises(exceptions.AmqpClosedConnection): - yield from amqp_connect(host=self.host, port=self.port, virtualhost='/unexistant', loop=self.loop) + await amqp_connect(host=self.host, port=self.port, virtualhost='/unexistant', loop=self.loop) def test_connection_wrong_login_password(self): with self.assertRaises(exceptions.AmqpClosedConnection): self.loop.run_until_complete(amqp_connect(host=self.host, port=self.port, login='wrong', password='wrong', loop=self.loop)) - @testing.coroutine - def test_connection_from_url(self): + async def test_connection_from_url(self): with mock.patch('aioamqp.connect') as connect: - @asyncio.coroutine - def func(*x, **y): + async def func(*x, **y): return 1, 2 connect.side_effect = func - yield from amqp_from_url('amqp://tom:pass@example.com:7777/myvhost', loop=self.loop) + await amqp_from_url('amqp://tom:pass@example.com:7777/myvhost', loop=self.loop) connect.assert_called_once_with( insist=False, password='pass', @@ -71,7 +66,6 @@ def func(*x, **y): loop=self.loop, ) - @testing.coroutine - def test_from_url_raises_on_wrong_scheme(self): + async def test_from_url_raises_on_wrong_scheme(self): with self.assertRaises(ValueError): - yield from amqp_from_url('invalid://') + await amqp_from_url('invalid://') diff --git a/aioamqp/tests/test_publish.py b/aioamqp/tests/test_publish.py index 47de887..0746178 100644 --- a/aioamqp/tests/test_publish.py +++ b/aioamqp/tests/test_publish.py @@ -9,100 +9,93 @@ class PublishTestCase(testcase.RabbitTestCase, unittest.TestCase): _multiprocess_can_split_ = True - @testing.coroutine - def test_publish(self): + async def test_publish(self): # declare - yield from self.channel.queue_declare("q", exclusive=True, no_wait=False) - yield from self.channel.exchange_declare("e", "fanout") - yield from self.channel.queue_bind("q", "e", routing_key='') + await self.channel.queue_declare("q", exclusive=True, no_wait=False) + await self.channel.exchange_declare("e", "fanout") + await self.channel.queue_bind("q", "e", routing_key='') # publish - yield from self.channel.publish("coucou", "e", routing_key='') + await self.channel.publish("coucou", "e", routing_key='') queues = self.list_queues() self.assertIn("q", queues) self.assertEqual(1, queues["q"]['messages']) - @testing.coroutine - def test_empty_publish(self): + async def test_empty_publish(self): # declare - yield from self.channel.queue_declare("q", exclusive=True, no_wait=False) - yield from self.channel.exchange_declare("e", "fanout") - yield from self.channel.queue_bind("q", "e", routing_key='') + await self.channel.queue_declare("q", exclusive=True, no_wait=False) + await self.channel.exchange_declare("e", "fanout") + await self.channel.queue_bind("q", "e", routing_key='') # publish - yield from self.channel.publish("", "e", routing_key='') + await self.channel.publish("", "e", routing_key='') queues = self.list_queues() self.assertIn("q", queues) self.assertEqual(1, queues["q"]["messages"]) self.assertEqual(0, queues["q"]["message_bytes"]) - @testing.coroutine - def test_big_publish(self): + async def test_big_publish(self): # declare - yield from self.channel.queue_declare("q", exclusive=True, no_wait=False) - yield from self.channel.exchange_declare("e", "fanout") - yield from self.channel.queue_bind("q", "e", routing_key='') + await self.channel.queue_declare("q", exclusive=True, no_wait=False) + await self.channel.exchange_declare("e", "fanout") + await self.channel.queue_bind("q", "e", routing_key='') # publish - yield from self.channel.publish("a"*1000000, "e", routing_key='') + await self.channel.publish("a"*1000000, "e", routing_key='') queues = self.list_queues() self.assertIn("q", queues) self.assertEqual(1, queues["q"]['messages']) - @testing.coroutine - def test_big_unicode_publish(self): + async def test_big_unicode_publish(self): # declare - yield from self.channel.queue_declare("q", exclusive=True, no_wait=False) - yield from self.channel.exchange_declare("e", "fanout") - yield from self.channel.queue_bind("q", "e", routing_key='') + await self.channel.queue_declare("q", exclusive=True, no_wait=False) + await self.channel.exchange_declare("e", "fanout") + await self.channel.queue_bind("q", "e", routing_key='') # publish - yield from self.channel.publish("Ы"*1000000, "e", routing_key='') - yield from self.channel.publish("Ы"*1000000, "e", routing_key='') + await self.channel.publish("Ы"*1000000, "e", routing_key='') + await self.channel.publish("Ы"*1000000, "e", routing_key='') queues = self.list_queues() self.assertIn("q", queues) self.assertEqual(2, queues["q"]['messages']) - @testing.coroutine - def test_confirmed_publish(self): + async def test_confirmed_publish(self): # declare - yield from self.channel.confirm_select() + await self.channel.confirm_select() self.assertTrue(self.channel.publisher_confirms) - yield from self.channel.queue_declare("q", exclusive=True, no_wait=False) - yield from self.channel.exchange_declare("e", "fanout") - yield from self.channel.queue_bind("q", "e", routing_key='') + await self.channel.queue_declare("q", exclusive=True, no_wait=False) + await self.channel.exchange_declare("e", "fanout") + await self.channel.queue_bind("q", "e", routing_key='') # publish - yield from self.channel.publish("coucou", "e", routing_key='') + await self.channel.publish("coucou", "e", routing_key='') queues = self.list_queues() self.assertIn("q", queues) self.assertEqual(1, queues["q"]['messages']) - @testing.coroutine - def test_return_from_publish(self): + async def test_return_from_publish(self): called = False - @asyncio.coroutine - def callback(channel, body, envelope, properties): + async def callback(channel, body, envelope, properties): nonlocal called called = True - channel = yield from self.amqp.channel(return_callback=callback) + channel = await self.amqp.channel(return_callback=callback) # declare - yield from channel.exchange_declare("e", "topic") + await channel.exchange_declare("e", "topic") # publish - yield from channel.publish("coucou", "e", routing_key="not.found", + await channel.publish("coucou", "e", routing_key="not.found", mandatory=True) for i in range(10): if called: break - yield from asyncio.sleep(0.1) + await asyncio.sleep(0.1) self.assertTrue(called) diff --git a/aioamqp/tests/test_queue.py b/aioamqp/tests/test_queue.py index 47b1965..3b76ec4 100644 --- a/aioamqp/tests/test_queue.py +++ b/aioamqp/tests/test_queue.py @@ -16,47 +16,41 @@ def setUp(self): super().setUp() self.consume_future = asyncio.Future(loop=self.loop) - @asyncio.coroutine - def callback(self, body, envelope, properties): + async def callback(self, body, envelope, properties): self.consume_future.set_result((body, envelope, properties)) - @asyncio.coroutine - def get_callback_result(self): - yield from self.consume_future + async def get_callback_result(self): + await self.consume_future result = self.consume_future.result() self.consume_future = asyncio.Future(loop=self.loop) return result - @testing.coroutine - def test_queue_declare_no_name(self): - result = yield from self.channel.queue_declare() + async def test_queue_declare_no_name(self): + result = await self.channel.queue_declare() self.assertIsNotNone(result['queue']) - @testing.coroutine - def test_queue_declare(self): + async def test_queue_declare(self): queue_name = 'queue_name' - result = yield from self.channel.queue_declare('queue_name') + result = await self.channel.queue_declare('queue_name') self.assertEqual(result['message_count'], 0) self.assertEqual(result['consumer_count'], 0) self.assertEqual(result['queue'].split('.')[-1], queue_name) self.assertTrue(result) - @testing.coroutine - def test_queue_declare_passive(self): + async def test_queue_declare_passive(self): queue_name = 'queue_name' - yield from self.channel.queue_declare('queue_name') - result = yield from self.channel.queue_declare(queue_name, passive=True) + await self.channel.queue_declare('queue_name') + result = await self.channel.queue_declare(queue_name, passive=True) self.assertEqual(result['message_count'], 0) self.assertEqual(result['consumer_count'], 0) self.assertEqual(result['queue'].split('.')[-1], queue_name) - @testing.coroutine - def test_queue_declare_custom_x_message_ttl_32_bits(self): + async def test_queue_declare_custom_x_message_ttl_32_bits(self): queue_name = 'queue_name' # 2147483648 == 10000000000000000000000000000000 # in binary, meaning it is 32 bit long x_message_ttl = 2147483648 - result = yield from self.channel.queue_declare('queue_name', arguments={ + result = await self.channel.queue_declare('queue_name', arguments={ 'x-message-ttl': x_message_ttl }) self.assertEqual(result['message_count'], 0) @@ -64,46 +58,42 @@ def test_queue_declare_custom_x_message_ttl_32_bits(self): self.assertEqual(result['queue'].split('.')[-1], queue_name) self.assertTrue(result) - @testing.coroutine - def test_queue_declare_passive_nonexistant_queue(self): + async def test_queue_declare_passive_nonexistant_queue(self): queue_name = 'queue_name' with self.assertRaises(exceptions.ChannelClosed) as cm: - yield from self.channel.queue_declare(queue_name, passive=True) + await self.channel.queue_declare(queue_name, passive=True) self.assertEqual(cm.exception.code, 404) - @testing.coroutine - def test_wrong_parameter_queue(self): + async def test_wrong_parameter_queue(self): queue_name = 'queue_name' - yield from self.channel.queue_declare(queue_name, exclusive=False, auto_delete=False) + await self.channel.queue_declare(queue_name, exclusive=False, auto_delete=False) with self.assertRaises(exceptions.ChannelClosed) as cm: - yield from self.channel.queue_declare(queue_name, + await self.channel.queue_declare(queue_name, passive=False, exclusive=True, auto_delete=True) self.assertEqual(cm.exception.code, 406) - @testing.coroutine - def test_multiple_channel_same_queue(self): + async def test_multiple_channel_same_queue(self): queue_name = 'queue_name' - channel1 = yield from self.amqp.channel() - channel2 = yield from self.amqp.channel() + channel1 = await self.amqp.channel() + channel2 = await self.amqp.channel() - result = yield from channel1.queue_declare(queue_name, passive=False) + result = await channel1.queue_declare(queue_name, passive=False) self.assertEqual(result['message_count'], 0) self.assertEqual(result['consumer_count'], 0) self.assertEqual(result['queue'].split('.')[-1], queue_name) - result = yield from channel2.queue_declare(queue_name, passive=False) + result = await channel2.queue_declare(queue_name, passive=False) self.assertEqual(result['message_count'], 0) self.assertEqual(result['consumer_count'], 0) self.assertEqual(result['queue'].split('.')[-1], queue_name) - @asyncio.coroutine - def _test_queue_declare(self, queue_name, exclusive=False, durable=False, auto_delete=False): + async def _test_queue_declare(self, queue_name, exclusive=False, durable=False, auto_delete=False): # declare queue - result = yield from self.channel.queue_declare( + result = await self.channel.queue_declare( queue_name, no_wait=False, exclusive=exclusive, durable=durable, auto_delete=auto_delete) @@ -123,7 +113,7 @@ def _test_queue_declare(self, queue_name, exclusive=False, durable=False, auto_d self.assertEqual(durable, queue['durable']) # delete queue - yield from self.safe_queue_delete(queue_name) + await self.safe_queue_delete(queue_name) def test_durable_and_auto_deleted(self): self.loop.run_until_complete( @@ -141,123 +131,113 @@ def test_not_durable_and_not_auto_deleted(self): self.loop.run_until_complete( self._test_queue_declare('q', exclusive=False, durable=False, auto_delete=False)) - @testing.coroutine - def test_exclusive(self): + async def test_exclusive(self): # create an exclusive queue - yield from self.channel.queue_declare("q", exclusive=True) + await self.channel.queue_declare("q", exclusive=True) # consume it - yield from self.channel.basic_consume(self.callback, queue_name="q", no_wait=False) + await self.channel.basic_consume(self.callback, queue_name="q", no_wait=False) # create an other amqp connection - _transport, protocol = yield from self.create_amqp() - channel = yield from self.create_channel(amqp=protocol) + _transport, protocol = await self.create_amqp() + channel = await self.create_channel(amqp=protocol) # assert that this connection cannot connect to the queue with self.assertRaises(exceptions.ChannelClosed): - yield from channel.basic_consume(self.callback, queue_name="q", no_wait=False) + await channel.basic_consume(self.callback, queue_name="q", no_wait=False) # amqp and channels are auto deleted by test case - @testing.coroutine - def test_not_exclusive(self): + async def test_not_exclusive(self): # create a non-exclusive queue - yield from self.channel.queue_declare('q', exclusive=False) + await self.channel.queue_declare('q', exclusive=False) # consume it - yield from self.channel.basic_consume(self.callback, queue_name='q', no_wait=False) + await self.channel.basic_consume(self.callback, queue_name='q', no_wait=False) # create an other amqp connection - _transport, protocol = yield from self.create_amqp() - channel = yield from self.create_channel(amqp=protocol) + _transport, protocol = await self.create_amqp() + channel = await self.create_channel(amqp=protocol) # assert that this connection can connect to the queue - yield from channel.basic_consume(self.callback, queue_name='q', no_wait=False) + await channel.basic_consume(self.callback, queue_name='q', no_wait=False) class QueueDeleteTestCase(testcase.RabbitTestCase, unittest.TestCase): - @testing.coroutine - def test_delete_queue(self): + async def test_delete_queue(self): queue_name = 'queue_name' - yield from self.channel.queue_declare(queue_name) - result = yield from self.channel.queue_delete(queue_name) + await self.channel.queue_declare(queue_name) + result = await self.channel.queue_delete(queue_name) self.assertTrue(result) - @testing.coroutine - def test_delete_inexistant_queue(self): + async def test_delete_inexistant_queue(self): queue_name = 'queue_name' if self.server_version() < (3, 3, 5): with self.assertRaises(exceptions.ChannelClosed) as cm: - result = yield from self.channel.queue_delete(queue_name) + result = await self.channel.queue_delete(queue_name) self.assertEqual(cm.exception.code, 404) else: - result = yield from self.channel.queue_delete(queue_name) + result = await self.channel.queue_delete(queue_name) self.assertTrue(result) class QueueBindTestCase(testcase.RabbitTestCase, unittest.TestCase): - @testing.coroutine - def test_bind_queue(self): + async def test_bind_queue(self): queue_name = 'queue_name' exchange_name = 'exchange_name' - yield from self.channel.queue_declare(queue_name) - yield from self.channel.exchange_declare(exchange_name, type_name='direct') + await self.channel.queue_declare(queue_name) + await self.channel.exchange_declare(exchange_name, type_name='direct') - result = yield from self.channel.queue_bind(queue_name, exchange_name, routing_key='') + result = await self.channel.queue_bind(queue_name, exchange_name, routing_key='') self.assertTrue(result) - @testing.coroutine - def test_bind_unexistant_exchange(self): + async def test_bind_unexistant_exchange(self): queue_name = 'queue_name' exchange_name = 'exchange_name' - yield from self.channel.queue_declare(queue_name) + await self.channel.queue_declare(queue_name) with self.assertRaises(exceptions.ChannelClosed) as cm: - yield from self.channel.queue_bind(queue_name, exchange_name, routing_key='') + await self.channel.queue_bind(queue_name, exchange_name, routing_key='') self.assertEqual(cm.exception.code, 404) - @testing.coroutine - def test_bind_unexistant_queue(self): + async def test_bind_unexistant_queue(self): queue_name = 'queue_name' exchange_name = 'exchange_name' - yield from self.channel.exchange_declare(exchange_name, type_name='direct') + await self.channel.exchange_declare(exchange_name, type_name='direct') with self.assertRaises(exceptions.ChannelClosed) as cm: - yield from self.channel.queue_bind(queue_name, exchange_name, routing_key='') + await self.channel.queue_bind(queue_name, exchange_name, routing_key='') self.assertEqual(cm.exception.code, 404) - @testing.coroutine - def test_unbind_queue(self): + async def test_unbind_queue(self): queue_name = 'queue_name' exchange_name = 'exchange_name' - yield from self.channel.queue_declare(queue_name) - yield from self.channel.exchange_declare(exchange_name, type_name='direct') + await self.channel.queue_declare(queue_name) + await self.channel.exchange_declare(exchange_name, type_name='direct') - yield from self.channel.queue_bind(queue_name, exchange_name, routing_key='') + await self.channel.queue_bind(queue_name, exchange_name, routing_key='') - result = yield from self.channel.queue_unbind(queue_name, exchange_name, routing_key='') + result = await self.channel.queue_unbind(queue_name, exchange_name, routing_key='') self.assertTrue(result) class QueuePurgeTestCase(testcase.RabbitTestCase, unittest.TestCase): - @testing.coroutine - def test_purge_queue(self): + async def test_purge_queue(self): queue_name = 'queue_name' - yield from self.channel.queue_declare(queue_name) - result = yield from self.channel.queue_purge(queue_name) + await self.channel.queue_declare(queue_name) + result = await self.channel.queue_purge(queue_name) self.assertEqual(result['message_count'], 0) - @testing.coroutine - def test_purge_queue_inexistant_queue(self): + async def test_purge_queue_inexistant_queue(self): queue_name = 'queue_name' with self.assertRaises(exceptions.ChannelClosed) as cm: - yield from self.channel.queue_purge(queue_name) + await self.channel.queue_purge(queue_name) self.assertEqual(cm.exception.code, 404) diff --git a/aioamqp/tests/test_recover.py b/aioamqp/tests/test_recover.py index e36d704..205feea 100644 --- a/aioamqp/tests/test_recover.py +++ b/aioamqp/tests/test_recover.py @@ -10,15 +10,12 @@ class RecoverTestCase(testcase.RabbitTestCase, unittest.TestCase): - @testing.coroutine - def test_basic_recover_async(self): - yield from self.channel.basic_recover_async(requeue=True) + async def test_basic_recover_async(self): + await self.channel.basic_recover_async(requeue=True) - @testing.coroutine - def test_basic_recover_async_no_requeue(self): - yield from self.channel.basic_recover_async(requeue=False) + async def test_basic_recover_async_no_requeue(self): + await self.channel.basic_recover_async(requeue=False) - @testing.coroutine - def test_basic_recover(self): - result = yield from self.channel.basic_recover(requeue=True) + async def test_basic_recover(self): + result = await self.channel.basic_recover(requeue=True) self.assertTrue(result) diff --git a/aioamqp/tests/test_server_basic_cancel.py b/aioamqp/tests/test_server_basic_cancel.py index 71c5c46..5a2527a 100644 --- a/aioamqp/tests/test_server_basic_cancel.py +++ b/aioamqp/tests/test_server_basic_cancel.py @@ -11,9 +11,8 @@ from . import testing -@asyncio.coroutine -def consumer(channel, body, envelope, properties): - yield from channel.basic_client_ack(envelope.delivery_tag) +async def consumer(channel, body, envelope, properties): + await channel.basic_client_ack(envelope.delivery_tag) class ServerBasicCancelTestCase(testcase.RabbitTestCase, unittest.TestCase): @@ -23,21 +22,18 @@ def setUp(self): super().setUp() self.queue_name = str(uuid.uuid4()) - @testing.coroutine - def test_cancel_whilst_consuming(self): - yield from self.channel.queue_declare(self.queue_name) + async def test_cancel_whilst_consuming(self): + await self.channel.queue_declare(self.queue_name) # None is non-callable. We want to make sure the callback is # unregistered and never called. - yield from self.channel.basic_consume(None) - yield from self.channel.queue_delete(self.queue_name) + await self.channel.basic_consume(None) + await self.channel.queue_delete(self.queue_name) - @testing.coroutine - def test_cancel_callbacks(self): + async def test_cancel_callbacks(self): callback_calls = [] - @asyncio.coroutine - def coroutine_callback(*args, **kwargs): + async def coroutine_callback(*args, **kwargs): callback_calls.append((args, kwargs)) def function_callback(*args, **kwargs): @@ -46,17 +42,16 @@ def function_callback(*args, **kwargs): self.channel.add_cancellation_callback(coroutine_callback) self.channel.add_cancellation_callback(function_callback) - yield from self.channel.queue_declare(self.queue_name) - rv = yield from self.channel.basic_consume(consumer) - yield from self.channel.queue_delete(self.queue_name) + await self.channel.queue_declare(self.queue_name) + rv = await self.channel.basic_consume(consumer) + await self.channel.queue_delete(self.queue_name) self.assertEqual(2, len(callback_calls)) for args, kwargs in callback_calls: self.assertIs(self.channel, args[0]) self.assertEqual(rv['consumer_tag'], args[1]) - @testing.coroutine - def test_cancel_callback_exceptions(self): + async def test_cancel_callback_exceptions(self): callback_calls = [] def function_callback(*args, **kwargs): @@ -66,9 +61,9 @@ def function_callback(*args, **kwargs): self.channel.add_cancellation_callback(function_callback) self.channel.add_cancellation_callback(function_callback) - yield from self.channel.queue_declare(self.queue_name) - yield from self.channel.basic_consume(consumer) - yield from self.channel.queue_delete(self.queue_name) + await self.channel.queue_declare(self.queue_name) + await self.channel.basic_consume(consumer) + await self.channel.queue_delete(self.queue_name) self.assertEqual(2, len(callback_calls)) self.assertTrue(self.channel.is_open) diff --git a/aioamqp/tests/testcase.py b/aioamqp/tests/testcase.py index 20a0fe7..3e20fe0 100644 --- a/aioamqp/tests/testcase.py +++ b/aioamqp/tests/testcase.py @@ -107,27 +107,25 @@ def reset_vhost(self): vname=self.vhost, username='guest', config='.*', rd='.*', wr='.*', ) - @asyncio.coroutine - def go(): - _transport, protocol = yield from self.create_amqp() - channel = yield from self.create_channel(amqp=protocol) + async def go(): + _transport, protocol = await self.create_amqp() + channel = await self.create_channel(amqp=protocol) self.channels.append(channel) self.loop.run_until_complete(go()) def tearDown(self): - @asyncio.coroutine - def go(): + async def go(): for queue_name, channel in self.queues.values(): logger.debug('Delete queue %s', self.full_name(queue_name)) - yield from self.safe_queue_delete(queue_name, channel) + await self.safe_queue_delete(queue_name, channel) for exchange_name, channel in self.exchanges.values(): logger.debug('Delete exchange %s', self.full_name(exchange_name)) - yield from self.safe_exchange_delete(exchange_name, channel) + await self.safe_exchange_delete(exchange_name, channel) for amqp in self.amqps: if amqp.state != OPEN: continue logger.debug('Delete amqp %s', amqp) - yield from amqp.close() + await amqp.close() del amqp self.loop.run_until_complete(go()) @@ -153,33 +151,29 @@ def server_version(self, amqp=None): server_version = tuple(int(x) for x in amqp.server_properties['version'].decode().split('.')) return server_version - @asyncio.coroutine - def check_exchange_exists(self, exchange_name): + async def check_exchange_exists(self, exchange_name): """Check if the exchange exist""" try: - yield from self.exchange_declare(exchange_name, passive=True) + await self.exchange_declare(exchange_name, passive=True) except exceptions.ChannelClosed: return False return True - @asyncio.coroutine - def assertExchangeExists(self, exchange_name): + async def assertExchangeExists(self, exchange_name): if not self.check_exchange_exists(exchange_name): self.fail("Exchange {} does not exists".format(exchange_name)) - @asyncio.coroutine - def check_queue_exists(self, queue_name): + async def check_queue_exists(self, queue_name): """Check if the queue exist""" try: - yield from self.queue_declare(queue_name, passive=True) + await self.queue_declare(queue_name, passive=True) except exceptions.ChannelClosed: return False return True - @asyncio.coroutine - def assertQueueExists(self, queue_name): + async def assertQueueExists(self, queue_name): if not self.check_queue_exists(queue_name): self.fail("Queue {} does not exists".format(queue_name)) @@ -197,8 +191,7 @@ def list_queues(self, vhost=None, fully_qualified_name=False): queues[queue_name] = queue_info return queues - @asyncio.coroutine - def safe_queue_delete(self, queue_name, channel=None): + async def safe_queue_delete(self, queue_name, channel=None): """Delete the queue but does not raise any exception if it fails The operation has a timeout as well. @@ -206,14 +199,13 @@ def safe_queue_delete(self, queue_name, channel=None): channel = channel or self.channel full_queue_name = self.full_name(queue_name) try: - yield from channel.queue_delete(full_queue_name, no_wait=False) + await channel.queue_delete(full_queue_name, no_wait=False) except asyncio.TimeoutError: logger.warning('Timeout on queue %s deletion', full_queue_name, exc_info=True) except Exception: # pylint: disable=broad-except logger.exception('Unexpected error on queue %s deletion', full_queue_name) - @asyncio.coroutine - def safe_exchange_delete(self, exchange_name, channel=None): + async def safe_exchange_delete(self, exchange_name, channel=None): """Delete the exchange but does not raise any exception if it fails The operation has a timeout as well. @@ -221,7 +213,7 @@ def safe_exchange_delete(self, exchange_name, channel=None): channel = channel or self.channel full_exchange_name = self.full_name(exchange_name) try: - yield from channel.exchange_delete(full_exchange_name, no_wait=False) + await channel.exchange_delete(full_exchange_name, no_wait=False) except asyncio.TimeoutError: logger.warning('Timeout on exchange %s deletion', full_exchange_name, exc_info=True) except Exception: # pylint: disable=broad-except @@ -240,29 +232,27 @@ def local_name(self, name): def is_full_name(self, name): return name.startswith(self.id()) - @asyncio.coroutine - def queue_declare(self, queue_name, *args, channel=None, safe_delete_before=True, **kw): + async def queue_declare(self, queue_name, *args, channel=None, safe_delete_before=True, **kw): channel = channel or self.channel if safe_delete_before: - yield from self.safe_queue_delete(queue_name, channel=channel) + await self.safe_queue_delete(queue_name, channel=channel) # prefix queue_name with the test name full_queue_name = self.full_name(queue_name) try: - rep = yield from channel.queue_declare(full_queue_name, *args, **kw) + rep = await channel.queue_declare(full_queue_name, *args, **kw) finally: self.queues[queue_name] = (queue_name, channel) return rep - @asyncio.coroutine - def exchange_declare(self, exchange_name, *args, channel=None, safe_delete_before=True, **kw): + async def exchange_declare(self, exchange_name, *args, channel=None, safe_delete_before=True, **kw): channel = channel or self.channel if safe_delete_before: - yield from self.safe_exchange_delete(exchange_name, channel=channel) + await self.safe_exchange_delete(exchange_name, channel=channel) # prefix exchange name full_exchange_name = self.full_name(exchange_name) try: - rep = yield from channel.exchange_declare(full_exchange_name, *args, **kw) + rep = await channel.exchange_declare(full_exchange_name, *args, **kw) finally: self.exchanges[exchange_name] = (exchange_name, channel) return rep @@ -270,18 +260,16 @@ def exchange_declare(self, exchange_name, *args, channel=None, safe_delete_befor def register_channel(self, channel): self.channels.append(channel) - @asyncio.coroutine - def create_channel(self, amqp=None): + async def create_channel(self, amqp=None): amqp = amqp or self.amqp - channel = yield from amqp.channel() + channel = await amqp.channel() return channel - @asyncio.coroutine - def create_amqp(self, vhost=None): + async def create_amqp(self, vhost=None): def protocol_factory(*args, **kw): return ProxyAmqpProtocol(self, *args, **kw) vhost = vhost or self.vhost - transport, protocol = yield from aioamqp_connect(host=self.host, port=self.port, virtualhost=vhost, + transport, protocol = await aioamqp_connect(host=self.host, port=self.port, virtualhost=vhost, protocol_factory=protocol_factory, loop=self.loop) self.amqps.append(protocol) return transport, protocol diff --git a/docs/api.rst b/docs/api.rst index 31ee506..d2f04c2 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -43,19 +43,18 @@ Starting a connection to AMQP really mean instanciate a new asyncio Protocol sub import asyncio import aioamqp - @asyncio.coroutine - def connect(): + async def connect(): try: - transport, protocol = yield from aioamqp.connect() # use default parameters + transport, protocol = await aioamqp.connect() # use default parameters except aioamqp.AmqpClosedConnection: print("closed connections") return print("connected !") - yield from asyncio.sleep(1) + await asyncio.sleep(1) print("close connection") - yield from protocol.close() + await protocol.close() transport.close() asyncio.get_event_loop().run_until_complete(connect()) @@ -93,14 +92,12 @@ The connect() method has an extra 'on_error' kwarg option. This on_error is a ca import socket import aioamqp - @asyncio.coroutine - def error_callback(exception): + async def error_callback(exception): print(exception) - @asyncio.coroutine - def connect(): + async def connect(): try: - transport, protocol = yield from aioamqp.connect( + transport, protocol = await aioamqp.connect( host='nonexistant.com', on_error=error_callback, client_properties={ @@ -122,13 +119,13 @@ Publishing messages A channel is the main object when you want to send message to an exchange, or to consume message from a queue:: - channel = yield from protocol.channel() + channel = await protocol.channel() When you want to produce some content, you declare a queue then publish message into it:: - yield from channel.queue_declare("my_queue") - yield from channel.publish("aioamqp hello", '', "my_queue") + await channel.queue_declare("my_queue") + await channel.publish("aioamqp hello", '', "my_queue") Note: we're pushing message to "my_queue" queue, through the default amqp exchange. @@ -141,12 +138,11 @@ When consuming message, you connect to the same queue you previously created:: import asyncio import aioamqp - @asyncio.coroutine - def callback(channel, body, envelope, properties): + async def callback(channel, body, envelope, properties): print(body) - channel = yield from protocol.channel() - yield from channel.basic_consume(callback, queue_name="my_queue") + channel = await protocol.channel() + await channel.basic_consume(callback, queue_name="my_queue") The ``basic_consume`` method tells the server to send us the messages, and will call ``callback`` with amqp response arguments. @@ -189,20 +185,18 @@ for additional details. ``aioamqp`` enables the extension for all channels but takes no action when the consumer is cancelled. Your application can be notified of consumer cancellations by adding a callback to the channel:: - @asyncio.coroutine - def consumer_cancelled(channel, consumer_tag): + async def consumer_cancelled(channel, consumer_tag): # implement required cleanup here pass - @asyncio.coroutine - def consumer(channel, body, envelope, properties): + async def consumer(channel, body, envelope, properties): channel.basic_ack(envelope.delivery_tag) - channel = yield from protocol.channel() + channel = await protocol.channel() channel.add_cancellation_callback(consumer_cancelled) - yield from channel.basic_consume(consumer, queue_name="my_queue") + await channel.basic_consume(consumer, queue_name="my_queue") The callback can be a simple callable or an asynchronous co-routine. It can be used to restart consumption on the channel, close the channel, or anything @@ -230,7 +224,7 @@ Here is an example to create a randomly named queue with special arguments `x-ma .. code-block:: python - result = yield from channel.queue_declare( + result = await channel.queue_declare( queue_name='', durable=True, arguments={'x-max-priority': 4} ) @@ -263,11 +257,11 @@ This simple example creates a `queue`, an `exchange` and bind them together. .. code-block:: python - channel = yield from protocol.channel() - yield from channel.queue_declare(queue_name='queue') - yield from channel.exchange_declare(exchange_name='exchange') + channel = await protocol.channel() + await channel.queue_declare(queue_name='queue') + await channel.exchange_declare(exchange_name='exchange') - yield from channel.queue_bind('queue', 'exchange', routing_key='') + await channel.queue_bind('queue', 'exchange', routing_key='') .. py:method:: Channel.queue_unbind(queue_name, exchange_name, routing_key, arguments, timeout) @@ -314,8 +308,8 @@ Note: the `internal` flag is deprecated and not used in this library. .. code-block:: python - channel = yield from protocol.channel() - yield from channel.exchange_declare(exchange_name='exchange', auto_delete=True) + channel = await protocol.channel() + await channel.exchange_declare(exchange_name='exchange', auto_delete=True) .. py:method:: Channel.exchange_delete(exchange_name, if_unused, no_wait, timeout) @@ -351,4 +345,3 @@ Note: the `internal` flag is deprecated and not used in this library. :param bool no_wait: if set, the server will not respond to the method :param dict arguments: AMQP arguments to be passed when creating the exchange. :param int timeout: wait for the server to respond after `timeout` - diff --git a/docs/examples/hello_world.rst b/docs/examples/hello_world.rst index 2eafe57..4614a3f 100644 --- a/docs/examples/hello_world.rst +++ b/docs/examples/hello_world.rst @@ -13,10 +13,9 @@ Creating a new connection: import asyncio import aioamqp - @asyncio.coroutine - def connect(): - transport, protocol = yield from aioamqp.connect() - channel = yield from protocol.channel() + async def connect(): + transport, protocol = await aioamqp.connect() + channel = await protocol.channel() asyncio.get_event_loop().run_until_complete(connect()) @@ -27,13 +26,13 @@ Now we have to declare a new queue to receive our messages: .. code-block:: python - yield from channel.queue_declare(queue_name='hello') + await channel.queue_declare(queue_name='hello') We're now ready to publish message on to this queue: .. code-block:: python - yield from channel.basic_publish( + await channel.basic_publish( payload='Hello World!', exchange_name='', routing_key='hello' @@ -45,7 +44,7 @@ We can now close the connection to rabbit: .. code-block:: python # close using the `AMQP` protocol - yield from protocol.close() + await protocol.close() # ensure the socket is closed. transport.close() @@ -60,16 +59,14 @@ We have to ensure the queue is created. Queue declaration is indempotant. .. code-block:: python - yield from channel.queue_declare(queue_name='hello') + await channel.queue_declare(queue_name='hello') To consume a message, the library calls a callback (which **MUST** be a coroutine): .. code-block:: python - @asyncio.coroutine - def callback(channel, body, envelope, properties): + async def callback(channel, body, envelope, properties): print(body) - yield from channel.basic_consume(callback, queue_name='hello', no_ack=True) - + await channel.basic_consume(callback, queue_name='hello', no_ack=True) diff --git a/docs/examples/publish_subscribe.rst b/docs/examples/publish_subscribe.rst index 0ccdc6a..f96210e 100644 --- a/docs/examples/publish_subscribe.rst +++ b/docs/examples/publish_subscribe.rst @@ -16,14 +16,14 @@ The publisher create a new `fanout` exchange: .. code-block:: python - yield from channel.exchange_declare(exchange_name='logs', type_name='fanout') + await channel.exchange_declare(exchange_name='logs', type_name='fanout') And publish message into that exchange: .. code-block:: python - yield from channel.basic_publish(message, exchange_name='logs', routing_key='') + await channel.basic_publish(message, exchange_name='logs', routing_key='') Consumer -------- @@ -32,11 +32,11 @@ The consumer create a temporary queue and binds it to the exchange. .. code-block:: python - yield from channel.exchange(exchange_name='logs', type_name='fanout') + await channel.exchange(exchange_name='logs', type_name='fanout') # let RabbitMQ generate a random queue name - result = yield from channel.queue(queue_name='', exclusive=True) + result = await channel.queue(queue_name='', exclusive=True) queue_name = result['queue'] - yield from channel.queue_bind(exchange_name='logs', queue_name=queue_name, routing_key='') + await channel.queue_bind(exchange_name='logs', queue_name=queue_name, routing_key='') diff --git a/docs/examples/routing.rst b/docs/examples/routing.rst index d4abe0b..ba208da 100644 --- a/docs/examples/routing.rst +++ b/docs/examples/routing.rst @@ -13,14 +13,14 @@ The publisher creater the `direct` exchange: .. code-block:: python - yield from channel.exchange(exchange_name='direct_logs', type_name='direct') + await channel.exchange(exchange_name='direct_logs', type_name='direct') Message are published into that exchange and routed using the severity for instance: .. code-block:: python - yield from channel.publish(message, exchange_name='direct_logs', routing_key='info') + await channel.publish(message, exchange_name='direct_logs', routing_key='info') Consumer @@ -30,7 +30,7 @@ The consumer may subscribe to multiple severities. To accomplish this purpose, i .. code-block:: python - result = yield from channel.queue(queue_name='', durable=False, auto_delete=True) + result = await channel.queue(queue_name='', durable=False, auto_delete=True) queue_name = result['queue'] @@ -40,7 +40,7 @@ The consumer may subscribe to multiple severities. To accomplish this purpose, i sys.exit(1) for severity in severities: - yield from channel.queue_bind( + await channel.queue_bind( exchange_name='direct_logs', queue_name=queue_name, routing_key=severity, diff --git a/docs/examples/rpc.rst b/docs/examples/rpc.rst index 13d4dd1..48a0686 100644 --- a/docs/examples/rpc.rst +++ b/docs/examples/rpc.rst @@ -9,7 +9,7 @@ The API will probably look like: .. code-block:: python fibonacci_rpc = FibonacciRpcClient() - result = yield from fibonacci_rpc.call(4) + result = await fibonacci_rpc.call(4) print("fib(4) is %r" % result) @@ -21,7 +21,7 @@ For that purpose, we publish a message to the `rpc_queue` and add a `reply_to` p .. code-block:: python - result = yield from channel.queue_declare(exclusive=True) + result = await channel.queue_declare(exclusive=True) callback_queue = result['queue'] channel.basic_publish( @@ -44,14 +44,13 @@ When unqueing a message, the server will publish a response directly in the call .. code-block:: python - @asyncio.coroutine - def on_request(channel, body, envelope, properties): + async def on_request(channel, body, envelope, properties): n = int(body) print(" [.] fib(%s)" % n) response = fib(n) - yield from channel.basic_publish( + await channel.basic_publish( payload=str(response), exchange_name='', routing_key=properties.reply_to, @@ -60,5 +59,4 @@ When unqueing a message, the server will publish a response directly in the call }, ) - yield from channel.basic_client_ack(delivery_tag=envelope.delivery_tag) - + await channel.basic_client_ack(delivery_tag=envelope.delivery_tag) diff --git a/docs/examples/topics.rst b/docs/examples/topics.rst index f420e31..5888083 100644 --- a/docs/examples/topics.rst +++ b/docs/examples/topics.rst @@ -11,10 +11,10 @@ The publisher prepares the exchange and publish messages using a routing_key whi .. code-block:: python - yield from channel.exchange('topic_logs', 'topic') + await channel.exchange('topic_logs', 'topic') - yield from yield from channel.publish(message, exchange_name=exchange_name, routing_key='anonymous.info') - yield from yield from channel.publish(message, exchange_name=exchange_name, routing_key='kern.critical') + await await channel.publish(message, exchange_name=exchange_name, routing_key='anonymous.info') + await await channel.publish(message, exchange_name=exchange_name, routing_key='kern.critical') @@ -27,7 +27,7 @@ The consumer selects the combination of 'facility'/'severity' he wants to subscr .. code-block:: python for binding_key in ("*.critical", "nginx.*"): - yield from channel.queue_bind( + await channel.queue_bind( exchange_name='topic_logs', queue_name=queue_name, routing_key=binding_key diff --git a/docs/examples/work_queue.rst b/docs/examples/work_queue.rst index c4d1087..9ee0621 100644 --- a/docs/examples/work_queue.rst +++ b/docs/examples/work_queue.rst @@ -10,9 +10,9 @@ This publisher creates a queue with the `durable` flag and publish a message wit .. code-block:: python - yield from channel.queue('task_queue', durable=True) + await channel.queue('task_queue', durable=True) - yield from channel.basic_publish( + await channel.basic_publish( payload=message, exchange_name='', routing_key='task_queue', @@ -31,14 +31,14 @@ The worker declares the queue with the exact same argument of the `new_task` pro .. code-block:: python - yield from channel.queue('task_queue', durable=True) + await channel.queue('task_queue', durable=True) Then, the worker configure the `QOS`: it specifies how the worker unqueues message. .. code-block:: python - yield from channel.basic_qos(prefetch_count=1, prefetch_size=0, connection_global=False) + await channel.basic_qos(prefetch_count=1, prefetch_size=0, connection_global=False) Finaly we have to create a callback that will `ack` the message to mark it as `processed`. @@ -47,10 +47,8 @@ You probably want to block the eventloop to simulate a CPU intensive task using .. code-block:: python - @asyncio.coroutine - def callback(channel, body, envelope, properties): + async def callback(channel, body, envelope, properties): print(" [x] Received %r" % body) - yield from asyncio.sleep(body.count(b'.')) + await asyncio.sleep(body.count(b'.')) print(" [x] Done") - yield from channel.basic_client_ack(delivery_tag=envelope.delivery_tag) - + await channel.basic_client_ack(delivery_tag=envelope.delivery_tag) diff --git a/examples/emit_log.py b/examples/emit_log.py index de757dc..396f263 100755 --- a/examples/emit_log.py +++ b/examples/emit_log.py @@ -12,26 +12,24 @@ import sys -@asyncio.coroutine -def exchange_routing(): +async def exchange_routing(): try: - transport, protocol = yield from aioamqp.connect('localhost', 5672) + transport, protocol = await aioamqp.connect('localhost', 5672) except aioamqp.AmqpClosedConnection: print("closed connections") return - channel = yield from protocol.channel() + channel = await protocol.channel() exchange_name = 'logs' message = ' '.join(sys.argv[1:]) or "info: Hello World!" - yield from channel.exchange_declare(exchange_name=exchange_name, type_name='fanout') + await channel.exchange_declare(exchange_name=exchange_name, type_name='fanout') - yield from channel.basic_publish(message, exchange_name=exchange_name, routing_key='') + await channel.basic_publish(message, exchange_name=exchange_name, routing_key='') print(" [x] Sent %r" % (message,)) - yield from protocol.close() + await protocol.close() transport.close() asyncio.get_event_loop().run_until_complete(exchange_routing()) - diff --git a/examples/emit_log_direct.py b/examples/emit_log_direct.py index c00c954..812574d 100755 --- a/examples/emit_log_direct.py +++ b/examples/emit_log_direct.py @@ -11,27 +11,25 @@ import sys -@asyncio.coroutine -def exchange_routing(): +async def exchange_routing(): try: - transport, protocol = yield from aioamqp.connect('localhost', 5672) + transport, protocol = await aioamqp.connect('localhost', 5672) except aioamqp.AmqpClosedConnection: print("closed connections") return - channel = yield from protocol.channel() + channel = await protocol.channel() exchange_name = 'direct_logs' severity = sys.argv[1] if len(sys.argv) > 1 else 'info' message = ' '.join(sys.argv[2:]) or 'Hello World!' - yield from channel.exchange(exchange_name, 'direct') + await channel.exchange(exchange_name, 'direct') - yield from channel.publish(message, exchange_name=exchange_name, routing_key=severity) + await channel.publish(message, exchange_name=exchange_name, routing_key=severity) print(" [x] Sent %r" % (message,)) - yield from protocol.close() + await protocol.close() transport.close() asyncio.get_event_loop().run_until_complete(exchange_routing()) - diff --git a/examples/emit_log_topic.py b/examples/emit_log_topic.py index 263d4c9..19a552a 100755 --- a/examples/emit_log_topic.py +++ b/examples/emit_log_topic.py @@ -11,25 +11,24 @@ import sys -@asyncio.coroutine -def exchange_routing_topic(): +async def exchange_routing_topic(): try: - transport, protocol = yield from aioamqp.connect('localhost', 5672) + transport, protocol = await aioamqp.connect('localhost', 5672) except aioamqp.AmqpClosedConnection: print("closed connections") return - channel = yield from protocol.channel() + channel = await protocol.channel() exchange_name = 'topic_logs' message = ' '.join(sys.argv[2:]) or 'Hello World!' routing_key = sys.argv[1] if len(sys.argv) > 1 else 'anonymous.info' - yield from channel.exchange(exchange_name, 'topic') + await channel.exchange(exchange_name, 'topic') - yield from channel.publish(message, exchange_name=exchange_name, routing_key=routing_key) + await channel.publish(message, exchange_name=exchange_name, routing_key=routing_key) print(" [x] Sent %r" % message) - yield from protocol.close() + await protocol.close() transport.close() diff --git a/examples/new_task.py b/examples/new_task.py index 4eec331..6255516 100755 --- a/examples/new_task.py +++ b/examples/new_task.py @@ -6,22 +6,21 @@ import sys -@asyncio.coroutine -def new_task(): +async def new_task(): try: - transport, protocol = yield from aioamqp.connect('localhost', 5672) + transport, protocol = await aioamqp.connect('localhost', 5672) except aioamqp.AmqpClosedConnection: print("closed connections") return - channel = yield from protocol.channel() + channel = await protocol.channel() - yield from channel.queue('task_queue', durable=True) + await channel.queue('task_queue', durable=True) message = ' '.join(sys.argv[1:]) or "Hello World!" - yield from channel.basic_publish( + await channel.basic_publish( payload=message, exchange_name='', routing_key='task_queue', @@ -31,10 +30,8 @@ def new_task(): ) print(" [x] Sent %r" % message,) - yield from protocol.close() + await protocol.close() transport.close() asyncio.get_event_loop().run_until_complete(new_task()) - - diff --git a/examples/receive.py b/examples/receive.py index 062b7bc..b1ab163 100644 --- a/examples/receive.py +++ b/examples/receive.py @@ -7,18 +7,16 @@ import aioamqp -@asyncio.coroutine -def callback(channel, body, envelope, properties): +async def callback(channel, body, envelope, properties): print(" [x] Received %r" % body) -@asyncio.coroutine -def receive(): - transport, protocol = yield from aioamqp.connect() - channel = yield from protocol.channel() +async def receive(): + transport, protocol = await aioamqp.connect() + channel = await protocol.channel() - yield from channel.queue_declare(queue_name='hello') + await channel.queue_declare(queue_name='hello') - yield from channel.basic_consume(callback, queue_name='hello') + await channel.basic_consume(callback, queue_name='hello') event_loop = asyncio.get_event_loop() diff --git a/examples/receive_log.py b/examples/receive_log.py index 9292fd8..eea3008 100644 --- a/examples/receive_log.py +++ b/examples/receive_log.py @@ -12,34 +12,32 @@ -@asyncio.coroutine -def callback(channel, body, envelope, properties): +async def callback(channel, body, envelope, properties): print(" [x] %r" % body) -@asyncio.coroutine -def receive_log(): +async def receive_log(): try: - transport, protocol = yield from aioamqp.connect('localhost', 5672) + transport, protocol = await aioamqp.connect('localhost', 5672) except aioamqp.AmqpClosedConnection: print("closed connections") return - channel = yield from protocol.channel() + channel = await protocol.channel() exchange_name = 'logs' - yield from channel.exchange(exchange_name=exchange_name, type_name='fanout') + await channel.exchange(exchange_name=exchange_name, type_name='fanout') # let RabbitMQ generate a random queue name - result = yield from channel.queue(queue_name='', exclusive=True) + result = await channel.queue(queue_name='', exclusive=True) queue_name = result['queue'] - yield from channel.queue_bind(exchange_name=exchange_name, queue_name=queue_name, routing_key='') + await channel.queue_bind(exchange_name=exchange_name, queue_name=queue_name, routing_key='') print(' [*] Waiting for logs. To exit press CTRL+C') - yield from channel.basic_consume(callback, queue_name=queue_name, no_ack=True) + await channel.basic_consume(callback, queue_name=queue_name, no_ack=True) event_loop = asyncio.get_event_loop() event_loop.run_until_complete(receive_log()) diff --git a/examples/receive_log_direct.py b/examples/receive_log_direct.py index b272d8b..f6c0708 100644 --- a/examples/receive_log_direct.py +++ b/examples/receive_log_direct.py @@ -13,24 +13,22 @@ import sys -@asyncio.coroutine -def callback(channel, body, envelope, properties): +async def callback(channel, body, envelope, properties): print("consumer {} recved {} ({})".format(envelope.consumer_tag, body, envelope.delivery_tag)) -@asyncio.coroutine -def receive_log(waiter): +async def receive_log(waiter): try: - transport, protocol = yield from aioamqp.connect('localhost', 5672) + transport, protocol = await aioamqp.connect('localhost', 5672) except aioamqp.AmqpClosedConnection: print("closed connections") return - channel = yield from protocol.channel() + channel = await protocol.channel() exchange_name = 'direct_logs' - yield from channel.exchange(exchange_name, 'direct') + await channel.exchange(exchange_name, 'direct') - result = yield from channel.queue(queue_name='', durable=False, auto_delete=True) + result = await channel.queue(queue_name='', durable=False, auto_delete=True) queue_name = result['queue'] @@ -40,7 +38,7 @@ def receive_log(waiter): sys.exit(1) for severity in severities: - yield from channel.queue_bind( + await channel.queue_bind( exchange_name='direct_logs', queue_name=queue_name, routing_key=severity, @@ -48,10 +46,10 @@ def receive_log(waiter): print(' [*] Waiting for logs. To exit press CTRL+C') - yield from asyncio.wait_for(channel.basic_consume(callback, queue_name=queue_name), timeout=10) - yield from waiter.wait() + await asyncio.wait_for(channel.basic_consume(callback, queue_name=queue_name), timeout=10) + await waiter.wait() - yield from protocol.close() + await protocol.close() transport.close() loop = asyncio.get_event_loop() diff --git a/examples/receive_log_topic.py b/examples/receive_log_topic.py index 079be2b..841c733 100644 --- a/examples/receive_log_topic.py +++ b/examples/receive_log_topic.py @@ -13,25 +13,23 @@ import sys -@asyncio.coroutine -def callback(channel, body, envelope, properties): +async def callback(channel, body, envelope, properties): print("consumer {} received {} ({})".format(envelope.consumer_tag, body, envelope.delivery_tag)) -@asyncio.coroutine -def receive_log(): +async def receive_log(): try: - transport, protocol = yield from aioamqp.connect('localhost', 5672) + transport, protocol = await aioamqp.connect('localhost', 5672) except aioamqp.AmqpClosedConnection: print("closed connections") return - channel = yield from protocol.channel() + channel = await protocol.channel() exchange_name = 'topic_logs' - yield from channel.exchange(exchange_name, 'topic') + await channel.exchange(exchange_name, 'topic') - result = yield from channel.queue(queue_name='', durable=False, auto_delete=True) + result = await channel.queue(queue_name='', durable=False, auto_delete=True) queue_name = result['queue'] binding_keys = sys.argv[1:] @@ -40,7 +38,7 @@ def receive_log(): sys.exit(1) for binding_key in binding_keys: - yield from channel.queue_bind( + await channel.queue_bind( exchange_name='topic_logs', queue_name=queue_name, routing_key=binding_key @@ -48,7 +46,7 @@ def receive_log(): print(' [*] Waiting for logs. To exit press CTRL+C') - yield from channel.basic_consume(callback, queue_name=queue_name) + await channel.basic_consume(callback, queue_name=queue_name) event_loop = asyncio.get_event_loop() event_loop.run_until_complete(receive_log()) diff --git a/examples/rpc_client.py b/examples/rpc_client.py index 9883c3e..e5982d3 100644 --- a/examples/rpc_client.py +++ b/examples/rpc_client.py @@ -19,35 +19,32 @@ def __init__(self): self.callback_queue = None self.waiter = asyncio.Event() - @asyncio.coroutine - def connect(self): + async def connect(self): """ an `__init__` method can't be a coroutine""" - self.transport, self.protocol = yield from aioamqp.connect() - self.channel = yield from self.protocol.channel() + self.transport, self.protocol = await aioamqp.connect() + self.channel = await self.protocol.channel() - result = yield from self.channel.queue_declare(queue_name='', exclusive=True) + result = await self.channel.queue_declare(queue_name='', exclusive=True) self.callback_queue = result['queue'] - yield from self.channel.basic_consume( + await self.channel.basic_consume( self.on_response, no_ack=True, queue_name=self.callback_queue, ) - @asyncio.coroutine - def on_response(self, channel, body, envelope, properties): + async def on_response(self, channel, body, envelope, properties): if self.corr_id == properties.correlation_id: self.response = body self.waiter.set() - @asyncio.coroutine - def call(self, n): + async def call(self, n): if not self.protocol: - yield from self.connect() + await self.connect() self.response = None self.corr_id = str(uuid.uuid4()) - yield from self.channel.basic_publish( + await self.channel.basic_publish( payload=str(n), exchange_name='', routing_key='rpc_queue', @@ -56,19 +53,17 @@ def call(self, n): 'correlation_id': self.corr_id, }, ) - yield from self.waiter.wait() + await self.waiter.wait() - yield from self.protocol.close() + await self.protocol.close() return int(self.response) -@asyncio.coroutine -def rpc_client(): +async def rpc_client(): fibonacci_rpc = FibonacciRpcClient() print(" [x] Requesting fib(30)") - response = yield from fibonacci_rpc.call(30) + response = await fibonacci_rpc.call(30) print(" [.] Got %r" % response) asyncio.get_event_loop().run_until_complete(rpc_client()) - diff --git a/examples/rpc_server.py b/examples/rpc_server.py index 42309b2..89cb16b 100644 --- a/examples/rpc_server.py +++ b/examples/rpc_server.py @@ -15,14 +15,13 @@ def fib(n): return fib(n-1) + fib(n-2) -@asyncio.coroutine -def on_request(channel, body, envelope, properties): +async def on_request(channel, body, envelope, properties): n = int(body) print(" [.] fib(%s)" % n) response = fib(n) - yield from channel.basic_publish( + await channel.basic_publish( payload=str(response), exchange_name='', routing_key=properties.reply_to, @@ -31,24 +30,21 @@ def on_request(channel, body, envelope, properties): }, ) - yield from channel.basic_client_ack(delivery_tag=envelope.delivery_tag) - + await channel.basic_client_ack(delivery_tag=envelope.delivery_tag) -@asyncio.coroutine -def rpc_server(): - transport, protocol = yield from aioamqp.connect() +async def rpc_server(): - channel = yield from protocol.channel() + transport, protocol = await aioamqp.connect() - yield from channel.queue_declare(queue_name='rpc_queue') - yield from channel.basic_qos(prefetch_count=1, prefetch_size=0, connection_global=False) - yield from channel.basic_consume(on_request, queue_name='rpc_queue') + channel = await protocol.channel() + + await channel.queue_declare(queue_name='rpc_queue') + await channel.basic_qos(prefetch_count=1, prefetch_size=0, connection_global=False) + await channel.basic_consume(on_request, queue_name='rpc_queue') print(" [x] Awaiting RPC requests") event_loop = asyncio.get_event_loop() event_loop.run_until_complete(rpc_server()) event_loop.run_forever() - - diff --git a/examples/send.py b/examples/send.py index 24c08fa..04b94c1 100644 --- a/examples/send.py +++ b/examples/send.py @@ -8,21 +8,20 @@ import aioamqp -@asyncio.coroutine -def send(): - transport, protocol = yield from aioamqp.connect() - channel = yield from protocol.channel() +async def send(): + transport, protocol = await aioamqp.connect() + channel = await protocol.channel() - yield from channel.queue_declare(queue_name='hello') + await channel.queue_declare(queue_name='hello') - yield from channel.basic_publish( + await channel.basic_publish( payload='Hello World!', exchange_name='', routing_key='hello' ) print(" [x] Sent 'Hello World!'") - yield from protocol.close() + await protocol.close() transport.close() diff --git a/examples/send_with_return.py b/examples/send_with_return.py index 9b13eb2..2fd1589 100644 --- a/examples/send_with_return.py +++ b/examples/send_with_return.py @@ -11,8 +11,7 @@ import aioamqp -@asyncio.coroutine -def handle_return(channel, body, envelope, properties): +async def handle_return(channel, body, envelope, properties): print('Got a returned message with routing key: {}.\n' 'Return code: {}\n' 'Return message: {}\n' @@ -20,14 +19,13 @@ def handle_return(channel, body, envelope, properties): envelope.reply_text, envelope.exchange_name)) -@asyncio.coroutine -def send(): - transport, protocol = yield from aioamqp.connect() - channel = yield from protocol.channel(return_callback=handle_return) +async def send(): + transport, protocol = await aioamqp.connect() + channel = await protocol.channel(return_callback=handle_return) - yield from channel.queue_declare(queue_name='hello') + await channel.queue_declare(queue_name='hello') - yield from channel.basic_publish( + await channel.basic_publish( payload='Hello World!', exchange_name='', routing_key='helo', # typo on purpose, will cause the return @@ -35,7 +33,7 @@ def send(): ) print(" [x] Sent 'Hello World!'") - yield from protocol.close() + await protocol.close() transport.close() diff --git a/examples/worker.py b/examples/worker.py index bfe530b..db59206 100644 --- a/examples/worker.py +++ b/examples/worker.py @@ -8,33 +8,28 @@ import sys -@asyncio.coroutine -def callback(channel, body, envelope, properties): +async def callback(channel, body, envelope, properties): print(" [x] Received %r" % body) - yield from asyncio.sleep(body.count(b'.')) + await asyncio.sleep(body.count(b'.')) print(" [x] Done") - yield from channel.basic_client_ack(delivery_tag=envelope.delivery_tag) + await channel.basic_client_ack(delivery_tag=envelope.delivery_tag) -@asyncio.coroutine -def worker(): +async def worker(): try: - transport, protocol = yield from aioamqp.connect('localhost', 5672) + transport, protocol = await aioamqp.connect('localhost', 5672) except aioamqp.AmqpClosedConnection: print("closed connections") return - channel = yield from protocol.channel() + channel = await protocol.channel() - yield from channel.queue(queue_name='task_queue', durable=True) - yield from channel.basic_qos(prefetch_count=1, prefetch_size=0, connection_global=False) - yield from channel.basic_consume(callback, queue_name='task_queue') + await channel.queue(queue_name='task_queue', durable=True) + await channel.basic_qos(prefetch_count=1, prefetch_size=0, connection_global=False) + await channel.basic_consume(callback, queue_name='task_queue') event_loop = asyncio.get_event_loop() event_loop.run_until_complete(worker()) event_loop.run_forever() - - - From 3cb1e57d8adf3849422ffbbb8f3eafefe419fff1 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Thu, 3 Jan 2019 18:11:22 +0100 Subject: [PATCH 31/72] migrate tests to asynctest --- aioamqp/tests/test_basic.py | 10 +++++----- aioamqp/tests/test_channel.py | 6 ++++-- aioamqp/tests/test_close.py | 4 ++-- aioamqp/tests/test_connect.py | 4 ++-- aioamqp/tests/test_connection_close.py | 4 ++-- aioamqp/tests/test_connection_lost.py | 6 +++--- aioamqp/tests/test_consume.py | 4 ++-- aioamqp/tests/test_exchange.py | 10 +++++----- aioamqp/tests/test_heartbeat.py | 4 ++-- aioamqp/tests/test_properties.py | 4 ++-- aioamqp/tests/test_protocol.py | 4 ++-- aioamqp/tests/test_publish.py | 4 ++-- aioamqp/tests/test_queue.py | 10 +++++----- aioamqp/tests/test_recover.py | 4 ++-- aioamqp/tests/test_server_basic_cancel.py | 4 ++-- aioamqp/tests/testcase.py | 2 +- aioamqp/tests/testing.py | 13 ------------- requirements_dev.txt | 2 +- 18 files changed, 44 insertions(+), 55 deletions(-) diff --git a/aioamqp/tests/test_basic.py b/aioamqp/tests/test_basic.py index 906ceb9..59279ff 100644 --- a/aioamqp/tests/test_basic.py +++ b/aioamqp/tests/test_basic.py @@ -4,7 +4,7 @@ import asyncio import struct -import unittest +import asynctest from . import testcase from . import testing @@ -12,7 +12,7 @@ from .. import properties -class QosTestCase(testcase.RabbitTestCase, unittest.TestCase): +class QosTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): async def test_basic_qos_default_args(self): result = await self.channel.basic_qos() @@ -43,7 +43,7 @@ async def test_basic_qos_wrong_values(self): connection_global=False) -class BasicCancelTestCase(testcase.RabbitTestCase, unittest.TestCase): +class BasicCancelTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): async def test_basic_cancel(self): @@ -72,7 +72,7 @@ async def test_basic_cancel_unknown_ctag(self): self.assertTrue(result) -class BasicGetTestCase(testcase.RabbitTestCase, unittest.TestCase): +class BasicGetTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): async def test_basic_get(self): @@ -106,7 +106,7 @@ async def test_basic_get_empty(self): await self.channel.basic_get(queue_name) -class BasicDeliveryTestCase(testcase.RabbitTestCase, unittest.TestCase): +class BasicDeliveryTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): async def publish(self, queue_name, exchange_name, routing_key, payload): diff --git a/aioamqp/tests/test_channel.py b/aioamqp/tests/test_channel.py index ee57448..c9ea8a0 100644 --- a/aioamqp/tests/test_channel.py +++ b/aioamqp/tests/test_channel.py @@ -5,13 +5,15 @@ import os import unittest +import asynctest + from . import testcase from . import testing from .. import exceptions IMPLEMENT_CHANNEL_FLOW = os.environ.get('IMPLEMENT_CHANNEL_FLOW', False) -class ChannelTestCase(testcase.RabbitTestCase, unittest.TestCase): +class ChannelTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): _multiprocess_can_split_ = True @@ -75,7 +77,7 @@ async def test_channel_active_inactive_flow(self): self.assertFalse(result['active']) -class ChannelIdTestCase(testcase.RabbitTestCase, unittest.TestCase): +class ChannelIdTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): async def test_channel_id_release_close(self): channels_count_start = self.amqp.channels_ids_count diff --git a/aioamqp/tests/test_close.py b/aioamqp/tests/test_close.py index 6ab807a..8586923 100644 --- a/aioamqp/tests/test_close.py +++ b/aioamqp/tests/test_close.py @@ -1,12 +1,12 @@ import asyncio -import unittest +import asynctest from . import testcase from . import testing from .. import exceptions -class CloseTestCase(testcase.RabbitTestCase, unittest.TestCase): +class CloseTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): def setUp(self): super().setUp() diff --git a/aioamqp/tests/test_connect.py b/aioamqp/tests/test_connect.py index 93bd5d7..7690f59 100644 --- a/aioamqp/tests/test_connect.py +++ b/aioamqp/tests/test_connect.py @@ -1,6 +1,6 @@ """Aioamqp tests""" -import unittest +import asynctest import socket from aioamqp import connect @@ -9,7 +9,7 @@ from . import testing, testcase -class AmqpConnectionTestCase(testcase.RabbitTestCase, unittest.TestCase): +class AmqpConnectionTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): async def test_connect(self): _transport, proto = await connect(host=self.host, port=self.port, virtualhost=self.vhost, loop=self.loop) diff --git a/aioamqp/tests/test_connection_close.py b/aioamqp/tests/test_connection_close.py index 4c64dd1..0716d8c 100644 --- a/aioamqp/tests/test_connection_close.py +++ b/aioamqp/tests/test_connection_close.py @@ -1,4 +1,4 @@ -import unittest +import asynctest from aioamqp.protocol import OPEN, CLOSED from aioamqp.exceptions import AmqpClosedConnection @@ -7,7 +7,7 @@ from . import testing -class CloseTestCase(testcase.RabbitTestCase, unittest.TestCase): +class CloseTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): async def test_close(self): amqp = self.amqp diff --git a/aioamqp/tests/test_connection_lost.py b/aioamqp/tests/test_connection_lost.py index adad88b..c081107 100644 --- a/aioamqp/tests/test_connection_lost.py +++ b/aioamqp/tests/test_connection_lost.py @@ -1,5 +1,5 @@ -import unittest -import unittest.mock +import asynctest +import asynctest.mock import asyncio from aioamqp.protocol import OPEN, CLOSED @@ -8,7 +8,7 @@ from . import testing -class ConnectionLostTestCase(testcase.RabbitTestCase, unittest.TestCase): +class ConnectionLostTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): _multiprocess_can_split_ = True diff --git a/aioamqp/tests/test_consume.py b/aioamqp/tests/test_consume.py index b313ac0..3c63d93 100644 --- a/aioamqp/tests/test_consume.py +++ b/aioamqp/tests/test_consume.py @@ -1,6 +1,6 @@ import asyncio -import unittest +import asynctest from . import testcase from . import testing @@ -8,7 +8,7 @@ from ..properties import Properties -class ConsumeTestCase(testcase.RabbitTestCase, unittest.TestCase): +class ConsumeTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): _multiprocess_can_split_ = True diff --git a/aioamqp/tests/test_exchange.py b/aioamqp/tests/test_exchange.py index cf1a684..13b66e8 100644 --- a/aioamqp/tests/test_exchange.py +++ b/aioamqp/tests/test_exchange.py @@ -3,14 +3,14 @@ """ import asyncio -import unittest +import asynctest from . import testcase from . import testing from .. import exceptions -class ExchangeDeclareTestCase(testcase.RabbitTestCase, unittest.TestCase): +class ExchangeDeclareTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): _multiprocess_can_split_ = True @@ -77,7 +77,7 @@ async def test_exchange_declare_unknown_type(self): auto_delete=False, durable=False, passive=True) -class ExchangeDelete(testcase.RabbitTestCase, unittest.TestCase): +class ExchangeDelete(testcase.RabbitTestCaseMixin, asynctest.TestCase): async def test_delete(self): exchange_name = 'exchange_name' @@ -108,7 +108,7 @@ async def test_double_delete(self): result = await self.channel.exchange_delete(exchange_name) self.assertTrue(result) -class ExchangeBind(testcase.RabbitTestCase, unittest.TestCase): +class ExchangeBind(testcase.RabbitTestCaseMixin, asynctest.TestCase): async def test_exchange_bind(self): await self.channel.exchange_declare('exchange_destination', type_name='direct') @@ -127,7 +127,7 @@ async def test_inexistant_exchange_bind(self): self.assertEqual(cm.exception.code, 404) -class ExchangeUnbind(testcase.RabbitTestCase, unittest.TestCase): +class ExchangeUnbind(testcase.RabbitTestCaseMixin, asynctest.TestCase): async def test_exchange_unbind(self): diff --git a/aioamqp/tests/test_heartbeat.py b/aioamqp/tests/test_heartbeat.py index fe32a17..4114987 100644 --- a/aioamqp/tests/test_heartbeat.py +++ b/aioamqp/tests/test_heartbeat.py @@ -3,7 +3,7 @@ """ import asyncio -import unittest +import asynctest from unittest import mock from aioamqp.protocol import CLOSED @@ -12,7 +12,7 @@ from . import testing -class HeartbeatTestCase(testcase.RabbitTestCase, unittest.TestCase): +class HeartbeatTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): async def test_heartbeat(self): with mock.patch.object( diff --git a/aioamqp/tests/test_properties.py b/aioamqp/tests/test_properties.py index e4e611d..faeefae 100644 --- a/aioamqp/tests/test_properties.py +++ b/aioamqp/tests/test_properties.py @@ -3,7 +3,7 @@ """ import asyncio -import unittest +import asynctest import logging from . import testcase @@ -13,7 +13,7 @@ logger = logging.getLogger(__name__) -class ReplyTestCase(testcase.RabbitTestCase, unittest.TestCase): +class ReplyTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): async def _server(self, server_future, exchange_name, routing_key): """Consume messages and reply to them by publishing messages back to the client using diff --git a/aioamqp/tests/test_protocol.py b/aioamqp/tests/test_protocol.py index 92c0933..c3996c9 100644 --- a/aioamqp/tests/test_protocol.py +++ b/aioamqp/tests/test_protocol.py @@ -3,7 +3,7 @@ """ import asyncio -import unittest +import asynctest from unittest import mock from . import testing @@ -14,7 +14,7 @@ from ..protocol import AmqpProtocol, OPEN -class ProtocolTestCase(testcase.RabbitTestCase, unittest.TestCase): +class ProtocolTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): async def test_connect(self): diff --git a/aioamqp/tests/test_publish.py b/aioamqp/tests/test_publish.py index 0746178..c51e03e 100644 --- a/aioamqp/tests/test_publish.py +++ b/aioamqp/tests/test_publish.py @@ -1,11 +1,11 @@ -import unittest +import asynctest import asyncio from . import testcase from . import testing -class PublishTestCase(testcase.RabbitTestCase, unittest.TestCase): +class PublishTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): _multiprocess_can_split_ = True diff --git a/aioamqp/tests/test_queue.py b/aioamqp/tests/test_queue.py index 3b76ec4..8240d78 100644 --- a/aioamqp/tests/test_queue.py +++ b/aioamqp/tests/test_queue.py @@ -3,14 +3,14 @@ """ import asyncio -import unittest +import asynctest from . import testcase from . import testing from .. import exceptions -class QueueDeclareTestCase(testcase.RabbitTestCase, unittest.TestCase): +class QueueDeclareTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): def setUp(self): super().setUp() @@ -157,7 +157,7 @@ async def test_not_exclusive(self): await channel.basic_consume(self.callback, queue_name='q', no_wait=False) -class QueueDeleteTestCase(testcase.RabbitTestCase, unittest.TestCase): +class QueueDeleteTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): async def test_delete_queue(self): @@ -178,7 +178,7 @@ async def test_delete_inexistant_queue(self): result = await self.channel.queue_delete(queue_name) self.assertTrue(result) -class QueueBindTestCase(testcase.RabbitTestCase, unittest.TestCase): +class QueueBindTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): async def test_bind_queue(self): @@ -225,7 +225,7 @@ async def test_unbind_queue(self): self.assertTrue(result) -class QueuePurgeTestCase(testcase.RabbitTestCase, unittest.TestCase): +class QueuePurgeTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): async def test_purge_queue(self): diff --git a/aioamqp/tests/test_recover.py b/aioamqp/tests/test_recover.py index 205feea..c0baf5f 100644 --- a/aioamqp/tests/test_recover.py +++ b/aioamqp/tests/test_recover.py @@ -2,13 +2,13 @@ Amqp basic tests for recover methods """ -import unittest +import asynctest from . import testcase from . import testing -class RecoverTestCase(testcase.RabbitTestCase, unittest.TestCase): +class RecoverTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): async def test_basic_recover_async(self): await self.channel.basic_recover_async(requeue=True) diff --git a/aioamqp/tests/test_server_basic_cancel.py b/aioamqp/tests/test_server_basic_cancel.py index 5a2527a..35028b5 100644 --- a/aioamqp/tests/test_server_basic_cancel.py +++ b/aioamqp/tests/test_server_basic_cancel.py @@ -4,7 +4,7 @@ """ import asyncio -import unittest.mock +import asynctest.mock import uuid from . import testcase @@ -15,7 +15,7 @@ async def consumer(channel, body, envelope, properties): await channel.basic_client_ack(envelope.delivery_tag) -class ServerBasicCancelTestCase(testcase.RabbitTestCase, unittest.TestCase): +class ServerBasicCancelTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): _multiprocess_can_split_ = True def setUp(self): diff --git a/aioamqp/tests/testcase.py b/aioamqp/tests/testcase.py index 3e20fe0..3881a91 100644 --- a/aioamqp/tests/testcase.py +++ b/aioamqp/tests/testcase.py @@ -73,7 +73,7 @@ def channel_factory(self, protocol, channel_id, return_callback=None): CHANNEL_FACTORY = channel_factory -class RabbitTestCase(testing.AsyncioTestCaseMixin): +class RabbitTestCaseMixin: """TestCase with a rabbit running in background""" RABBIT_TIMEOUT = 1.0 diff --git a/aioamqp/tests/testing.py b/aioamqp/tests/testing.py index b0c5f87..e09c0fc 100644 --- a/aioamqp/tests/testing.py +++ b/aioamqp/tests/testing.py @@ -41,16 +41,3 @@ def wrapper(self): if handler.messages: raise AsyncioErrors(handler.messages) return wrapper - - -class AsyncioTestCaseMixin: - __timeout__ = 10 - - def setUp(self): - super().setUp() - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(self.loop) - - def tearDown(self): - super().tearDown() - self.loop.close() diff --git a/requirements_dev.txt b/requirements_dev.txt index 1786cf1..abd58d4 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,5 +1,5 @@ -e . - +asynctest coverage pylint pytest>4 From 9d31638e8e9d18b0bec94752ed9e078a671382f2 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Thu, 3 Jan 2019 18:19:52 +0100 Subject: [PATCH 32/72] Remove dead code --- aioamqp/exceptions.py | 4 ++-- aioamqp/tests/testing.py | 19 ------------------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/aioamqp/exceptions.py b/aioamqp/exceptions.py index 076fd40..07bd390 100644 --- a/aioamqp/exceptions.py +++ b/aioamqp/exceptions.py @@ -6,15 +6,15 @@ class AioamqpException(Exception): pass -class ConfigurationError(AioamqpException): - pass class AmqpClosedConnection(AioamqpException): pass + class SynchronizationError(AioamqpException): pass + class EmptyQueue(AioamqpException): pass diff --git a/aioamqp/tests/testing.py b/aioamqp/tests/testing.py index e09c0fc..759eddf 100644 --- a/aioamqp/tests/testing.py +++ b/aioamqp/tests/testing.py @@ -22,22 +22,3 @@ def emit(self, record): asyncio_logger = logging.getLogger('asyncio') handler = Handler() asyncio_logger.addHandler(handler) - - -def timeout(t): - def wrapper(func): - setattr(func, '__timeout__', t) - return func - return wrapper - - -def coroutine(func): - @wraps(func) - def wrapper(self): - handler.messages = [] - coro = asyncio.coroutine(func) - timeout_ = getattr(func, '__timeout__', self.__timeout__) - self.loop.run_until_complete(asyncio.wait_for(coro(self), timeout=timeout_, loop=self.loop)) - if handler.messages: - raise AsyncioErrors(handler.messages) - return wrapper From a5d5932dc646cbfdf66ebcb81ec6c293952c0fc1 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Thu, 3 Jan 2019 18:21:02 +0100 Subject: [PATCH 33/72] tests: remove bad callback args test --- aioamqp/tests/test_consume.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/aioamqp/tests/test_consume.py b/aioamqp/tests/test_consume.py index 3c63d93..abf20dd 100644 --- a/aioamqp/tests/test_consume.py +++ b/aioamqp/tests/test_consume.py @@ -26,31 +26,6 @@ async def get_callback_result(self): self.consume_future = asyncio.Future(loop=self.loop) return result - async def test_wrong_callback_argument(self): - - def badcallback(): - pass - - await self.channel.queue_declare("q", exclusive=True, no_wait=False) - await self.channel.exchange_declare("e", "fanout") - await self.channel.queue_bind("q", "e", routing_key='') - - # get a different channel - channel = await self.create_channel() - - # publish - await channel.publish("coucou", "e", routing_key='',) - - # assert there is a message to consume - queues = self.list_queues() - self.assertIn("q", queues) - self.assertEqual(1, queues["q"]['messages']) - - await asyncio.sleep(2, loop=self.loop) - # start consume - with self.assertRaises(exceptions.ConfigurationError): - await channel.basic_consume(badcallback, queue_name="q") - async def test_consume(self): # declare await self.channel.queue_declare("q", exclusive=True, no_wait=False) From 88f71bc4da1f3ae09c5ecf0438c79dfeed2fb864 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Thu, 3 Jan 2019 18:33:08 +0100 Subject: [PATCH 34/72] Update changelog --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 4667c21..e710b2c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,6 +6,7 @@ Next release * Uses pamqp to encode or decode protocol frames. * Drops support of python 3.3 and python 3.4. + * Uses async and await keywords. Aioamqp 0.12.0 -------------- From 8607c6ab910ee0fe8807fb60e4d97fc2b8cfc4c2 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Fri, 4 Jan 2019 09:43:03 +0100 Subject: [PATCH 35/72] Removes compat module, uses asyncio.ensure_future --- aioamqp/compat.py | 11 ----------- aioamqp/protocol.py | 7 +++---- 2 files changed, 3 insertions(+), 15 deletions(-) delete mode 100644 aioamqp/compat.py diff --git a/aioamqp/compat.py b/aioamqp/compat.py deleted file mode 100644 index 3259974..0000000 --- a/aioamqp/compat.py +++ /dev/null @@ -1,11 +0,0 @@ -""" - Compatibility between python or package versions -""" -# pylint: disable=unused-import - -import asyncio - -try: - from asyncio import ensure_future -except ImportError: - ensure_future = getattr(asyncio, 'async') diff --git a/aioamqp/protocol.py b/aioamqp/protocol.py index 6578b30..37d8479 100644 --- a/aioamqp/protocol.py +++ b/aioamqp/protocol.py @@ -14,7 +14,6 @@ from . import frame as amqp_frame from . import exceptions from . import version -from .compat import ensure_future logger = logging.getLogger(__name__) @@ -244,7 +243,7 @@ async def start_connection(self, host, port, login, password, virtualhost, ssl=F await self.ensure_open() # for now, we read server's responses asynchronously - self.worker = ensure_future(self.run(), loop=self._loop) + self.worker = asyncio.ensure_future(self.run(), loop=self._loop) async def get_frame(self): """Read the frame, and only decode its header @@ -305,7 +304,7 @@ def _close_channels(self, reply_code=None, reply_text=None, exception=None): if self._on_error_callback: if asyncio.iscoroutinefunction(self._on_error_callback): - ensure_future(self._on_error_callback(exception), loop=self._loop) + asyncio.ensure_future(self._on_error_callback(exception), loop=self._loop) else: self._on_error_callback(exceptions.ChannelClosed(exception)) @@ -367,7 +366,7 @@ def _heartbeat_timer_send_reset(self): self.server_heartbeat, self._heartbeat_trigger_send.set) if self._heartbeat_worker is None: - self._heartbeat_worker = ensure_future(self._heartbeat(), loop=self._loop) + self._heartbeat_worker = asyncio.ensure_future(self._heartbeat(), loop=self._loop) def _heartbeat_stop(self): self.server_heartbeat = None From 854b237970d870cf9efa68e73090f87c380ef4d0 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Thu, 17 Nov 2016 11:49:06 +0100 Subject: [PATCH 36/72] adds pylint tests --- .travis.yml | 2 +- Makefile | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2807a2f..b915d0c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ install: - pip install -r requirements_dev.txt - pip install --editable . - pip freeze -script: make test +script: "make test && make pylint" notifications: email: true irc: diff --git a/Makefile b/Makefile index ab1414f..6b6bb84 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,9 @@ test: update: pip install -r requirements_dev.txt +pylint: + pylint aioamqp + ### semi-private targets used by polyconseil's CI (copy-pasted from blease) ### From 247083440a29a260e5f3e9b67e20cd16b820b791 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Thu, 17 Nov 2016 12:04:32 +0100 Subject: [PATCH 37/72] pylint: disable wrong-import-order --- .pylintrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index d5b1838..7c6d5da 100644 --- a/.pylintrc +++ b/.pylintrc @@ -47,7 +47,8 @@ disable= too-many-locals, too-many-public-methods, too-many-statements, - unused-argument + unused-argument, + wrong-import-order [REPORTS] From 8b6fc08df176dc5ff8e8ae9e1db96d79e509dfb9 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Thu, 17 Nov 2016 13:55:20 +0100 Subject: [PATCH 38/72] properties: disable redefined-builtin --- aioamqp/properties.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aioamqp/properties.py b/aioamqp/properties.py index 27abd3e..43ab642 100644 --- a/aioamqp/properties.py +++ b/aioamqp/properties.py @@ -1,3 +1,4 @@ +# pylint: disable=redefined-builtin from .constants import MESSAGE_PROPERTIES From f96e207f0e56be2e9dcae7f9ba470accf480f885 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Fri, 4 Jan 2019 10:02:25 +0100 Subject: [PATCH 39/72] Fix pylints --- aioamqp/channel.py | 6 ++++-- aioamqp/exceptions.py | 2 ++ aioamqp/protocol.py | 8 +++----- aioamqp/tests/test_basic.py | 2 -- aioamqp/tests/test_channel.py | 1 - aioamqp/tests/test_close.py | 1 - aioamqp/tests/test_connect.py | 2 +- aioamqp/tests/test_connection_close.py | 1 - aioamqp/tests/test_connection_lost.py | 1 - aioamqp/tests/test_consume.py | 1 - aioamqp/tests/test_exchange.py | 2 -- aioamqp/tests/test_heartbeat.py | 1 - aioamqp/tests/test_properties.py | 1 - aioamqp/tests/test_protocol.py | 10 ++++++---- aioamqp/tests/test_publish.py | 3 +-- aioamqp/tests/test_queue.py | 1 - aioamqp/tests/test_recover.py | 1 - aioamqp/tests/test_server_basic_cancel.py | 5 ++--- aioamqp/tests/testcase.py | 1 - aioamqp/tests/testing.py | 4 +--- 20 files changed, 20 insertions(+), 34 deletions(-) diff --git a/aioamqp/channel.py b/aioamqp/channel.py index 0848de5..97742ad 100644 --- a/aioamqp/channel.py +++ b/aioamqp/channel.py @@ -120,7 +120,8 @@ async def _write_frame(self, channel_id, request, check_open=True, drain=True): if drain: await self.protocol._drain() - async def _write_frame_awaiting_response(self, waiter_id, channel_id, request, no_wait, check_open=True, drain=True): + async def _write_frame_awaiting_response(self, waiter_id, channel_id, request, + no_wait, check_open=True, drain=True): '''Write a frame and set a waiter for the response (unless no_wait is set)''' if no_wait: await self._write_frame(channel_id, request, check_open=check_open, drain=drain) @@ -393,7 +394,8 @@ async def queue_purge_ok(self, frame): ## Basic class implementation # - async def basic_publish(self, payload, exchange_name, routing_key, properties=None, mandatory=False, immediate=False): + async def basic_publish(self, payload, exchange_name, routing_key, + properties=None, mandatory=False, immediate=False): if isinstance(payload, str): warnings.warn("Str payload support will be removed in next release", DeprecationWarning) payload = payload.encode() diff --git a/aioamqp/exceptions.py b/aioamqp/exceptions.py index 07bd390..aef1bfa 100644 --- a/aioamqp/exceptions.py +++ b/aioamqp/exceptions.py @@ -32,12 +32,14 @@ def __init__(self, code=0, message='Channel is closed'): class DuplicateConsumerTag(AioamqpException): def __repr__(self): + # pylint: disable=unsubscriptable-object return ('The consumer tag specified already exists for this ' 'channel: %s' % self.args[0]) class ConsumerCancelled(AioamqpException): def __repr__(self): + # pylint: disable=unsubscriptable-object return ('The consumer %s has been cancelled' % self.args[0]) diff --git a/aioamqp/protocol.py b/aioamqp/protocol.py index 37d8479..9f80158 100644 --- a/aioamqp/protocol.py +++ b/aioamqp/protocol.py @@ -25,14 +25,12 @@ class _StreamWriter(asyncio.StreamWriter): def write(self, data): - ret = super().write(data) + super().write(data) self._protocol._heartbeat_timer_send_reset() - return ret def writelines(self, data): - ret = super().writelines(data) + super().writelines(data) self._protocol._heartbeat_timer_send_reset() - return ret def write_eof(self): ret = super().write_eof() @@ -267,7 +265,7 @@ async def dispatch_frame(self, frame_channel=None, frame=None): if isinstance(frame, pamqp.heartbeat.Heartbeat): return - if frame_channel is not 0: + if frame_channel != 0: channel = self.channels.get(frame_channel) if channel is not None: await channel.dispatch_frame(frame) diff --git a/aioamqp/tests/test_basic.py b/aioamqp/tests/test_basic.py index 59279ff..d59ce19 100644 --- a/aioamqp/tests/test_basic.py +++ b/aioamqp/tests/test_basic.py @@ -3,11 +3,9 @@ """ import asyncio -import struct import asynctest from . import testcase -from . import testing from .. import exceptions from .. import properties diff --git a/aioamqp/tests/test_channel.py b/aioamqp/tests/test_channel.py index c9ea8a0..325a2cd 100644 --- a/aioamqp/tests/test_channel.py +++ b/aioamqp/tests/test_channel.py @@ -8,7 +8,6 @@ import asynctest from . import testcase -from . import testing from .. import exceptions IMPLEMENT_CHANNEL_FLOW = os.environ.get('IMPLEMENT_CHANNEL_FLOW', False) diff --git a/aioamqp/tests/test_close.py b/aioamqp/tests/test_close.py index 8586923..159be16 100644 --- a/aioamqp/tests/test_close.py +++ b/aioamqp/tests/test_close.py @@ -2,7 +2,6 @@ import asynctest from . import testcase -from . import testing from .. import exceptions diff --git a/aioamqp/tests/test_connect.py b/aioamqp/tests/test_connect.py index 7690f59..22e5c62 100644 --- a/aioamqp/tests/test_connect.py +++ b/aioamqp/tests/test_connect.py @@ -6,7 +6,7 @@ from aioamqp import connect from aioamqp.protocol import OPEN -from . import testing, testcase +from . import testcase class AmqpConnectionTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): diff --git a/aioamqp/tests/test_connection_close.py b/aioamqp/tests/test_connection_close.py index 0716d8c..9491548 100644 --- a/aioamqp/tests/test_connection_close.py +++ b/aioamqp/tests/test_connection_close.py @@ -4,7 +4,6 @@ from aioamqp.exceptions import AmqpClosedConnection from . import testcase -from . import testing class CloseTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): diff --git a/aioamqp/tests/test_connection_lost.py b/aioamqp/tests/test_connection_lost.py index c081107..21c7819 100644 --- a/aioamqp/tests/test_connection_lost.py +++ b/aioamqp/tests/test_connection_lost.py @@ -5,7 +5,6 @@ from aioamqp.protocol import OPEN, CLOSED from . import testcase -from . import testing class ConnectionLostTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): diff --git a/aioamqp/tests/test_consume.py b/aioamqp/tests/test_consume.py index abf20dd..2b587bb 100644 --- a/aioamqp/tests/test_consume.py +++ b/aioamqp/tests/test_consume.py @@ -3,7 +3,6 @@ import asynctest from . import testcase -from . import testing from .. import exceptions from ..properties import Properties diff --git a/aioamqp/tests/test_exchange.py b/aioamqp/tests/test_exchange.py index 13b66e8..2475acb 100644 --- a/aioamqp/tests/test_exchange.py +++ b/aioamqp/tests/test_exchange.py @@ -2,11 +2,9 @@ Amqp exchange class tests """ -import asyncio import asynctest from . import testcase -from . import testing from .. import exceptions diff --git a/aioamqp/tests/test_heartbeat.py b/aioamqp/tests/test_heartbeat.py index 4114987..7024955 100644 --- a/aioamqp/tests/test_heartbeat.py +++ b/aioamqp/tests/test_heartbeat.py @@ -9,7 +9,6 @@ from aioamqp.protocol import CLOSED from . import testcase -from . import testing class HeartbeatTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): diff --git a/aioamqp/tests/test_properties.py b/aioamqp/tests/test_properties.py index faeefae..804572c 100644 --- a/aioamqp/tests/test_properties.py +++ b/aioamqp/tests/test_properties.py @@ -7,7 +7,6 @@ import logging from . import testcase -from . import testing logger = logging.getLogger(__name__) diff --git a/aioamqp/tests/test_protocol.py b/aioamqp/tests/test_protocol.py index c3996c9..4abe3b3 100644 --- a/aioamqp/tests/test_protocol.py +++ b/aioamqp/tests/test_protocol.py @@ -2,11 +2,9 @@ Test our Protocol class """ -import asyncio import asynctest from unittest import mock -from . import testing from . import testcase from .. import exceptions from .. import connect as amqp_connect @@ -18,7 +16,9 @@ class ProtocolTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): async def test_connect(self): - _transport, protocol = await amqp_connect(host=self.host, port=self.port, virtualhost=self.vhost, loop=self.loop) + _transport, protocol = await amqp_connect( + host=self.host, port=self.port, virtualhost=self.vhost, loop=self.loop + ) self.assertEqual(protocol.state, OPEN) await protocol.close() @@ -44,7 +44,9 @@ async def test_connection_unexistant_vhost(self): def test_connection_wrong_login_password(self): with self.assertRaises(exceptions.AmqpClosedConnection): - self.loop.run_until_complete(amqp_connect(host=self.host, port=self.port, login='wrong', password='wrong', loop=self.loop)) + self.loop.run_until_complete( + amqp_connect(host=self.host, port=self.port, login='wrong', password='wrong', loop=self.loop) + ) async def test_connection_from_url(self): with mock.patch('aioamqp.connect') as connect: diff --git a/aioamqp/tests/test_publish.py b/aioamqp/tests/test_publish.py index c51e03e..bcce9fd 100644 --- a/aioamqp/tests/test_publish.py +++ b/aioamqp/tests/test_publish.py @@ -2,7 +2,6 @@ import asyncio from . import testcase -from . import testing class PublishTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): @@ -93,7 +92,7 @@ async def callback(channel, body, envelope, properties): await channel.publish("coucou", "e", routing_key="not.found", mandatory=True) - for i in range(10): + for _i in range(10): if called: break await asyncio.sleep(0.1) diff --git a/aioamqp/tests/test_queue.py b/aioamqp/tests/test_queue.py index 8240d78..f5ac996 100644 --- a/aioamqp/tests/test_queue.py +++ b/aioamqp/tests/test_queue.py @@ -6,7 +6,6 @@ import asynctest from . import testcase -from . import testing from .. import exceptions diff --git a/aioamqp/tests/test_recover.py b/aioamqp/tests/test_recover.py index c0baf5f..f08d1da 100644 --- a/aioamqp/tests/test_recover.py +++ b/aioamqp/tests/test_recover.py @@ -5,7 +5,6 @@ import asynctest from . import testcase -from . import testing class RecoverTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): diff --git a/aioamqp/tests/test_server_basic_cancel.py b/aioamqp/tests/test_server_basic_cancel.py index 35028b5..dd9c1a7 100644 --- a/aioamqp/tests/test_server_basic_cancel.py +++ b/aioamqp/tests/test_server_basic_cancel.py @@ -3,12 +3,11 @@ """ -import asyncio +import asynctest import asynctest.mock import uuid from . import testcase -from . import testing async def consumer(channel, body, envelope, properties): @@ -47,7 +46,7 @@ def function_callback(*args, **kwargs): await self.channel.queue_delete(self.queue_name) self.assertEqual(2, len(callback_calls)) - for args, kwargs in callback_calls: + for args, _kwargs in callback_calls: self.assertIs(self.channel, args[0]) self.assertEqual(rv['consumer_tag'], args[1]) diff --git a/aioamqp/tests/testcase.py b/aioamqp/tests/testcase.py index 3881a91..82c5da4 100644 --- a/aioamqp/tests/testcase.py +++ b/aioamqp/tests/testcase.py @@ -12,7 +12,6 @@ import pyrabbit2.api -from . import testing from .. import connect as aioamqp_connect from .. import exceptions from ..channel import Channel diff --git a/aioamqp/tests/testing.py b/aioamqp/tests/testing.py index 759eddf..a47edf8 100644 --- a/aioamqp/tests/testing.py +++ b/aioamqp/tests/testing.py @@ -1,11 +1,9 @@ -from functools import wraps import logging -import asyncio - class AsyncioErrors(AssertionError): def __repr__(self): + # pylint: disable=unsubscriptable-object return " Date: Tue, 8 Jan 2019 13:37:50 +0100 Subject: [PATCH 40/72] Makefile: fix jenkins-test target --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 6b6bb84..54587b1 100644 --- a/Makefile +++ b/Makefile @@ -46,9 +46,9 @@ reports: mkdir -p reports jenkins-test: reports - $(MAKE) test TEST_OPTIONS="--with-coverage --cover-package=$(PACKAGE) \ - --cover-xml --cover-xml-file=reports/xmlcov.xml \ - --with-xunit --xunit-file=reports/TEST-$(PACKAGE).xml \ + $(MAKE) test TEST_OPTIONS="--cov=$(PACKAGE) \ + --cov-report xml:reports/xmlcov.xml \ + --junitxml=reports/TEST-$(PACKAGE).xml \ -v \ $(TEST_OPTIONS)" From 3951f8357c193eded59b4c1f9b50bb1931e6febb Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Mon, 14 Jan 2019 10:51:16 +0100 Subject: [PATCH 41/72] Makefile: update target force the latest version available --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 54587b1..83d7c4a 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ test: update: - pip install -r requirements_dev.txt + pip install --upgrade -r requirements_dev.txt pylint: pylint aioamqp From 2b5869028f545deec99a4c9fb660c0eee9118cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Cardona?= Date: Sat, 26 Jan 2019 16:22:21 +0100 Subject: [PATCH 42/72] Makefile: update pip cli call for new versions --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 83d7c4a..a54cbbd 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ test: update: - pip install --upgrade -r requirements_dev.txt + pip install --upgrade --upgrade-strategy=eager -r requirements_dev.txt pylint: pylint aioamqp From 6640ab4ebf5d8e09388bddb77c670f3ab4276903 Mon Sep 17 00:00:00 2001 From: Denis Shushkevich Date: Thu, 16 Nov 2017 11:39:12 +0300 Subject: [PATCH 43/72] Warn "Connection lost" only if it losts unexpectedly Closes #155. --- aioamqp/protocol.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aioamqp/protocol.py b/aioamqp/protocol.py index 9f80158..d7256a8 100644 --- a/aioamqp/protocol.py +++ b/aioamqp/protocol.py @@ -111,7 +111,8 @@ def eof_received(self): return False def connection_lost(self, exc): - logger.warning("Connection lost exc=%r", exc) + if exc is not None: + logger.warning("Connection lost exc=%r", exc) self.connection_closed.set() self.state = CLOSED self._close_channels(exception=exc) From 9fc130b94e55e929032ee1d2dc564fd0882e7334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Cardona?= Date: Mon, 28 Jan 2019 10:44:25 +0100 Subject: [PATCH 44/72] tests: rely on pytest-timeout to catch runaway tests --- Makefile | 2 +- aioamqp/tests/testcase.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index a54cbbd..771c477 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ PACKAGE = aioamqp TEST_LAUNCHER ?= pytest -TEST_OPTIONS ?= -v -s --timeout=20 +TEST_OPTIONS ?= -v -s --timeout=60 PYLINT_RC ?= .pylintrc BUILD_DIR ?= build diff --git a/aioamqp/tests/testcase.py b/aioamqp/tests/testcase.py index 82c5da4..120104b 100644 --- a/aioamqp/tests/testcase.py +++ b/aioamqp/tests/testcase.py @@ -84,7 +84,7 @@ def setUp(self): self.port = os.environ.get('AMQP_PORT', 5672) self.vhost = os.environ.get('AMQP_VHOST', self.VHOST + str(uuid.uuid4())) self.http_client = pyrabbit2.api.Client( - '{HOST}:15672'.format(HOST=self.host), 'guest', 'guest', timeout=20 + '{HOST}:15672'.format(HOST=self.host), 'guest', 'guest', timeout=None, ) self.amqps = [] From 1eec060aeaf25eeb5368dfebe6684470372d0476 Mon Sep 17 00:00:00 2001 From: Paul Wistrand Date: Thu, 8 Jun 2017 13:46:40 +0300 Subject: [PATCH 45/72] Allow ssl.SSLContext instance to be supplied to connect and from_url mirroring loop.create_connection() --- aioamqp/__init__.py | 5 ++-- aioamqp/tests/test_protocol.py | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/aioamqp/__init__.py b/aioamqp/__init__.py index 057d09a..24442e0 100644 --- a/aioamqp/__init__.py +++ b/aioamqp/__init__.py @@ -40,7 +40,7 @@ async def connect(host='localhost', port=None, login='guest', password='guest', create_connection_kwargs = {} if ssl: - ssl_context = ssl_module.create_default_context() + ssl_context = ssl_module.create_default_context() if isinstance(ssl, bool) else ssl if not verify_ssl: ssl_context.check_hostname = False ssl_context.verify_mode = ssl_module.CERT_NONE @@ -104,10 +104,9 @@ async def from_url( login=url.username or 'guest', password=url.password or 'guest', virtualhost=(url.path[1:] if len(url.path) > 1 else '/'), - ssl=(url.scheme == 'amqps'), login_method=login_method, insist=insist, protocol_factory=protocol_factory, verify_ssl=verify_ssl, - **kwargs) + **dict(kwargs, ssl=kwargs.get('ssl', url.scheme == 'amqps'))) return transport, protocol diff --git a/aioamqp/tests/test_protocol.py b/aioamqp/tests/test_protocol.py index 4abe3b3..3c99902 100644 --- a/aioamqp/tests/test_protocol.py +++ b/aioamqp/tests/test_protocol.py @@ -4,6 +4,7 @@ import asynctest from unittest import mock +import ssl as ssl_module from . import testcase from .. import exceptions @@ -68,6 +69,47 @@ async def func(*x, **y): loop=self.loop, ) + async def test_ssl_connection_from_url(self): + with mock.patch('aioamqp.connect') as connect: + async def func(*x, **y): + return 1, 2 + connect.side_effect = func + await amqp_from_url('amqps://tom:pass@example.com:7777/myvhost', loop=self.loop) + connect.assert_called_once_with( + insist=False, + password='pass', + login_method='AMQPLAIN', + ssl=True, + login='tom', + host='example.com', + protocol_factory=AmqpProtocol, + virtualhost='myvhost', + port=7777, + verify_ssl=True, + loop=self.loop, + ) + + async def test_ssl_context_connection_from_url(self): + ssl_context = ssl_module.create_default_context() + with mock.patch('aioamqp.connect') as connect: + async def func(*x, **y): + return 1, 2 + connect.side_effect = func + await amqp_from_url('amqp://tom:pass@example.com:7777/myvhost', loop=self.loop, ssl=ssl_context) + connect.assert_called_once_with( + insist=False, + password='pass', + login_method='AMQPLAIN', + ssl=ssl_context, + login='tom', + host='example.com', + protocol_factory=AmqpProtocol, + virtualhost='myvhost', + port=7777, + verify_ssl=True, + loop=self.loop, + ) + async def test_from_url_raises_on_wrong_scheme(self): with self.assertRaises(ValueError): await amqp_from_url('invalid://') From 7360aed426bec507fa47dca0cf34483e4e3ab534 Mon Sep 17 00:00:00 2001 From: Paul Wistrand Date: Thu, 8 Jun 2017 14:33:57 +0300 Subject: [PATCH 46/72] fix: to failing test in python 3.3 --- aioamqp/tests/test_protocol.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/aioamqp/tests/test_protocol.py b/aioamqp/tests/test_protocol.py index 3c99902..fef73b8 100644 --- a/aioamqp/tests/test_protocol.py +++ b/aioamqp/tests/test_protocol.py @@ -4,7 +4,6 @@ import asynctest from unittest import mock -import ssl as ssl_module from . import testcase from .. import exceptions @@ -90,7 +89,7 @@ async def func(*x, **y): ) async def test_ssl_context_connection_from_url(self): - ssl_context = ssl_module.create_default_context() + ssl_context = mock.Mock() with mock.patch('aioamqp.connect') as connect: async def func(*x, **y): return 1, 2 From 2a9ca18e6577e86bfcbc10582a9cb9ea864f683d Mon Sep 17 00:00:00 2001 From: notmeta Date: Wed, 23 Jan 2019 12:19:26 +0000 Subject: [PATCH 47/72] ssl argument is now only of type SSLContext --- aioamqp/__init__.py | 23 +++++++++++------------ aioamqp/tests/test_protocol.py | 24 +----------------------- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/aioamqp/__init__.py b/aioamqp/__init__.py index 24442e0..5075e5a 100644 --- a/aioamqp/__init__.py +++ b/aioamqp/__init__.py @@ -1,6 +1,5 @@ import asyncio import socket -import sys import ssl as ssl_module # import as to enable argument named ssl in connect from urllib.parse import urlparse @@ -12,7 +11,7 @@ async def connect(host='localhost', port=None, login='guest', password='guest', - virtualhost='/', ssl=False, login_method='AMQPLAIN', insist=False, + virtualhost='/', ssl=None, login_method='AMQPLAIN', insist=False, protocol_factory=AmqpProtocol, *, verify_ssl=True, loop=None, **kwargs): """Convenient method to connect to an AMQP broker @@ -21,7 +20,8 @@ async def connect(host='localhost', port=None, login='guest', password='guest', @login: login @password: password @virtualhost: AMQP virtualhost to use for this connection - @ssl: Create an SSL connection instead of a plain unencrypted one + @ssl: SSL context used for secure connections, omit for no SSL + - see https://docs.python.org/3/library/ssl.html @verify_ssl: Verify server's SSL certificate (True by default) @login_method: AMQP auth method @insist: Insist on connecting to a server @@ -39,12 +39,11 @@ async def connect(host='localhost', port=None, login='guest', password='guest', create_connection_kwargs = {} - if ssl: - ssl_context = ssl_module.create_default_context() if isinstance(ssl, bool) else ssl + if ssl is not None: if not verify_ssl: - ssl_context.check_hostname = False - ssl_context.verify_mode = ssl_module.CERT_NONE - create_connection_kwargs['ssl'] = ssl_context + ssl.check_hostname = False + ssl.verify_mode = ssl_module.CERT_NONE + create_connection_kwargs['ssl'] = ssl if port is None: if ssl: @@ -65,13 +64,13 @@ async def connect(host='localhost', port=None, login='guest', password='guest', sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) try: - await protocol.start_connection(host, port, login, password, virtualhost, ssl=ssl, - login_method=login_method, insist=insist) + await protocol.start_connection(host, port, login, password, virtualhost, ssl=ssl, login_method=login_method, + insist=insist) except Exception: await protocol.wait_closed() raise - return (transport, protocol) + return transport, protocol async def from_url( @@ -108,5 +107,5 @@ async def from_url( insist=insist, protocol_factory=protocol_factory, verify_ssl=verify_ssl, - **dict(kwargs, ssl=kwargs.get('ssl', url.scheme == 'amqps'))) + **kwargs) return transport, protocol diff --git a/aioamqp/tests/test_protocol.py b/aioamqp/tests/test_protocol.py index fef73b8..2239811 100644 --- a/aioamqp/tests/test_protocol.py +++ b/aioamqp/tests/test_protocol.py @@ -1,7 +1,6 @@ """ Test our Protocol class """ - import asynctest from unittest import mock @@ -14,7 +13,6 @@ class ProtocolTestCase(testcase.RabbitTestCaseMixin, asynctest.TestCase): - async def test_connect(self): _transport, protocol = await amqp_connect( host=self.host, port=self.port, virtualhost=self.vhost, loop=self.loop @@ -68,33 +66,13 @@ async def func(*x, **y): loop=self.loop, ) - async def test_ssl_connection_from_url(self): - with mock.patch('aioamqp.connect') as connect: - async def func(*x, **y): - return 1, 2 - connect.side_effect = func - await amqp_from_url('amqps://tom:pass@example.com:7777/myvhost', loop=self.loop) - connect.assert_called_once_with( - insist=False, - password='pass', - login_method='AMQPLAIN', - ssl=True, - login='tom', - host='example.com', - protocol_factory=AmqpProtocol, - virtualhost='myvhost', - port=7777, - verify_ssl=True, - loop=self.loop, - ) - async def test_ssl_context_connection_from_url(self): ssl_context = mock.Mock() with mock.patch('aioamqp.connect') as connect: async def func(*x, **y): return 1, 2 connect.side_effect = func - await amqp_from_url('amqp://tom:pass@example.com:7777/myvhost', loop=self.loop, ssl=ssl_context) + await amqp_from_url('amqps://tom:pass@example.com:7777/myvhost', loop=self.loop, ssl=ssl_context) connect.assert_called_once_with( insist=False, password='pass', From c075fd43ea8d0933bfe8e817e6712f1cd476e301 Mon Sep 17 00:00:00 2001 From: notmeta Date: Wed, 23 Jan 2019 12:38:30 +0000 Subject: [PATCH 48/72] Fixed test_connection_from_url --- aioamqp/tests/test_protocol.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aioamqp/tests/test_protocol.py b/aioamqp/tests/test_protocol.py index 2239811..63481bd 100644 --- a/aioamqp/tests/test_protocol.py +++ b/aioamqp/tests/test_protocol.py @@ -56,7 +56,6 @@ async def func(*x, **y): insist=False, password='pass', login_method='AMQPLAIN', - ssl=False, login='tom', host='example.com', protocol_factory=AmqpProtocol, From d9b2f2dc64f2a77e1862e25150d3afa2d5ffa601 Mon Sep 17 00:00:00 2001 From: notmeta Date: Thu, 24 Jan 2019 08:40:34 +0000 Subject: [PATCH 49/72] Removed `verify_ssl` parameter from connect/from_url --- aioamqp/__init__.py | 12 ++---------- aioamqp/tests/test_protocol.py | 2 -- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/aioamqp/__init__.py b/aioamqp/__init__.py index 5075e5a..3e67936 100644 --- a/aioamqp/__init__.py +++ b/aioamqp/__init__.py @@ -1,6 +1,5 @@ import asyncio import socket -import ssl as ssl_module # import as to enable argument named ssl in connect from urllib.parse import urlparse from .exceptions import * # pylint: disable=wildcard-import @@ -12,7 +11,7 @@ async def connect(host='localhost', port=None, login='guest', password='guest', virtualhost='/', ssl=None, login_method='AMQPLAIN', insist=False, - protocol_factory=AmqpProtocol, *, verify_ssl=True, loop=None, **kwargs): + protocol_factory=AmqpProtocol, *, loop=None, **kwargs): """Convenient method to connect to an AMQP broker @host: the host to connect to @@ -22,7 +21,6 @@ async def connect(host='localhost', port=None, login='guest', password='guest', @virtualhost: AMQP virtualhost to use for this connection @ssl: SSL context used for secure connections, omit for no SSL - see https://docs.python.org/3/library/ssl.html - @verify_ssl: Verify server's SSL certificate (True by default) @login_method: AMQP auth method @insist: Insist on connecting to a server @protocol_factory: @@ -40,9 +38,6 @@ async def connect(host='localhost', port=None, login='guest', password='guest', create_connection_kwargs = {} if ssl is not None: - if not verify_ssl: - ssl.check_hostname = False - ssl.verify_mode = ssl_module.CERT_NONE create_connection_kwargs['ssl'] = ssl if port is None: @@ -74,8 +69,7 @@ async def connect(host='localhost', port=None, login='guest', password='guest', async def from_url( - url, login_method='AMQPLAIN', insist=False, protocol_factory=AmqpProtocol, *, - verify_ssl=True, **kwargs): + url, login_method='AMQPLAIN', insist=False, protocol_factory=AmqpProtocol, **kwargs): """ Connect to the AMQP using a single url parameter and return the client. For instance: @@ -85,7 +79,6 @@ async def from_url( @insist: Insist on connecting to a server @protocol_factory: Factory to use, if you need to subclass AmqpProtocol - @verify_ssl: Verify server's SSL certificate (True by default) @loop: optionally set the event loop to use. @kwargs: Arguments to be given to the protocol_factory instance @@ -106,6 +99,5 @@ async def from_url( login_method=login_method, insist=insist, protocol_factory=protocol_factory, - verify_ssl=verify_ssl, **kwargs) return transport, protocol diff --git a/aioamqp/tests/test_protocol.py b/aioamqp/tests/test_protocol.py index 63481bd..766a5d9 100644 --- a/aioamqp/tests/test_protocol.py +++ b/aioamqp/tests/test_protocol.py @@ -61,7 +61,6 @@ async def func(*x, **y): protocol_factory=AmqpProtocol, virtualhost='myvhost', port=7777, - verify_ssl=True, loop=self.loop, ) @@ -82,7 +81,6 @@ async def func(*x, **y): protocol_factory=AmqpProtocol, virtualhost='myvhost', port=7777, - verify_ssl=True, loop=self.loop, ) From dadfc226ff16e39d86c27f3fdb870392b734e038 Mon Sep 17 00:00:00 2001 From: notmeta Date: Thu, 24 Jan 2019 12:24:03 +0000 Subject: [PATCH 50/72] Added pycharm ide files to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 7cbf5f7..4611c2f 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ docs/_build/ # editor stuffs *.swp + +# Pycharm/IntelliJ +.idea/* \ No newline at end of file From 2b783b54074090065787fe18c3e0149cd8611824 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Tue, 29 Jan 2019 16:38:30 +0100 Subject: [PATCH 51/72] Update changelog --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index e710b2c..72f64f8 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,7 @@ Changelog Next release ------------ + * SSL Connections must be configured with an SSLContext object in ``connect`` and ``from_url`` (closes #142). * Uses pamqp to encode or decode protocol frames. * Drops support of python 3.3 and python 3.4. * Uses async and await keywords. From 6df74db9ff214da930be0c16ced462016f7beccc Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Tue, 29 Jan 2019 16:56:06 +0100 Subject: [PATCH 52/72] Update Autors --- AUTHORS.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 7799e39..649d422 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -22,3 +22,5 @@ AUTHORS are (and/or have been):: * Mads Sejersen * Dave Shawley * Jacob Hagstedt P Suorra + * Corey `notmeta` + * Paul Wistrand From 8d6550fec4e6f8259d607621548561f14c7e423d Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Thu, 24 Jan 2019 13:43:17 +0100 Subject: [PATCH 53/72] protocol: replace `with await` with `async with` --- aioamqp/protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aioamqp/protocol.py b/aioamqp/protocol.py index d7256a8..939ba82 100644 --- a/aioamqp/protocol.py +++ b/aioamqp/protocol.py @@ -143,7 +143,7 @@ async def ensure_open(self): raise exceptions.AioamqpException("connection isn't established yet.") async def _drain(self): - with (await self._drain_lock): + async with self._drain_lock: # drain() cannot be called concurrently by multiple coroutines: # http://bugs.python.org/issue29930. Remove this lock when no # version of Python where this bugs exists is supported anymore. From 1b107b5991e44b9599ff10bb55d6e0bf0cceec02 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Wed, 30 Jan 2019 10:20:55 +0100 Subject: [PATCH 54/72] readme: Remove nose requirements --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 90d66f1..3d38dfa 100644 --- a/README.rst +++ b/README.rst @@ -28,7 +28,7 @@ Tests require an instance of RabbitMQ. You can start a new instance using docker docker run -d --log-driver=syslog -e RABBITMQ_NODENAME=my-rabbit --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management -Then you can run the tests with ``make test`` (requires ``nose``). +Then you can run the tests with ``make test``. tests using docker-compose ^^^^^^^^^^^^^^^^^^^^^^^^^^ From c87ce90117c5de4c03d73448d9ff35060a41d583 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Wed, 27 Feb 2019 12:09:59 +0100 Subject: [PATCH 55/72] Remove TODO about login method. Create issue instead --- aioamqp/protocol.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aioamqp/protocol.py b/aioamqp/protocol.py index 939ba82..9ee1ce3 100644 --- a/aioamqp/protocol.py +++ b/aioamqp/protocol.py @@ -187,7 +187,6 @@ async def start_connection(self, host, port, login, password, virtualhost, ssl=F """ if login_method != 'PLAIN': - # TODO logger.warning('only PLAIN login_method is supported, falling back to AMQPLAIN') self._stream_writer.write(amqp_constants.PROTOCOL_HEADER) From 017b1218b4f9a1cfb9141e67c1600ef25eaa7795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Mon, 18 Mar 2019 10:29:30 +0100 Subject: [PATCH 56/72] Add keywords to setup.py. This should increase visibility on PyPI, where asyncio/RabbitMQ libraries are currently hard to find. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 1ed1a36..1ec3eae 100644 --- a/setup.py +++ b/setup.py @@ -19,6 +19,7 @@ url='https://github.com/polyconseil/aioamqp', description=description, long_description=open('README.rst').read(), + keywords=['asyncio', 'amqp', 'rabbitmq', 'aio'], download_url='https://pypi.python.org/pypi/aioamqp', packages=[ 'aioamqp', From fd5a73fef2a8d2e5e05056fd20d21b72142c9bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Cardona?= Date: Wed, 27 Mar 2019 14:19:46 +0100 Subject: [PATCH 57/72] tests: add sync primitives to channel cancellation tests to prevent flakiness --- aioamqp/tests/test_server_basic_cancel.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/aioamqp/tests/test_server_basic_cancel.py b/aioamqp/tests/test_server_basic_cancel.py index dd9c1a7..b9d2452 100644 --- a/aioamqp/tests/test_server_basic_cancel.py +++ b/aioamqp/tests/test_server_basic_cancel.py @@ -3,6 +3,8 @@ """ +import asyncio + import asynctest import asynctest.mock import uuid @@ -31,12 +33,14 @@ async def test_cancel_whilst_consuming(self): async def test_cancel_callbacks(self): callback_calls = [] + callback_event = asyncio.Event() async def coroutine_callback(*args, **kwargs): callback_calls.append((args, kwargs)) def function_callback(*args, **kwargs): callback_calls.append((args, kwargs)) + callback_event.set() self.channel.add_cancellation_callback(coroutine_callback) self.channel.add_cancellation_callback(function_callback) @@ -45,6 +49,7 @@ def function_callback(*args, **kwargs): rv = await self.channel.basic_consume(consumer) await self.channel.queue_delete(self.queue_name) + await callback_event.wait() self.assertEqual(2, len(callback_calls)) for args, _kwargs in callback_calls: self.assertIs(self.channel, args[0]) @@ -52,9 +57,11 @@ def function_callback(*args, **kwargs): async def test_cancel_callback_exceptions(self): callback_calls = [] + callback_event = asyncio.Event() def function_callback(*args, **kwargs): callback_calls.append((args, kwargs)) + callback_event.set() raise RuntimeError self.channel.add_cancellation_callback(function_callback) @@ -64,5 +71,6 @@ def function_callback(*args, **kwargs): await self.channel.basic_consume(consumer) await self.channel.queue_delete(self.queue_name) + await callback_event.wait() self.assertEqual(2, len(callback_calls)) self.assertTrue(self.channel.is_open) From 96d9d75eb111c5a13c96265dc185b9ae1586fdc8 Mon Sep 17 00:00:00 2001 From: jpVm5jYYRE1VIKL Date: Mon, 11 Mar 2019 10:49:13 +0100 Subject: [PATCH 58/72] doc: fix consumer example --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index d2f04c2..e3c5a2f 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -191,7 +191,7 @@ of consumer cancellations by adding a callback to the channel:: async def consumer(channel, body, envelope, properties): - channel.basic_ack(envelope.delivery_tag) + await channel.basic_client_ack(envelope.delivery_tag) channel = await protocol.channel() From df1058a72dac4413515685e6651da1c8f36d5bb8 Mon Sep 17 00:00:00 2001 From: Quentin BEY Date: Fri, 19 Apr 2019 12:37:03 +0200 Subject: [PATCH 59/72] aioamqp.frame: `_frame_parts` becomes `frame_parts` since pamqp 2.2.0 see: https://github.com/gmr/pamqp/commit/7297f96d247a13a6cedff531f9f005e7980cc013 --- aioamqp/frame.py | 2 +- docs/changelog.rst | 1 + setup.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/aioamqp/frame.py b/aioamqp/frame.py index bbd3db4..d70cfd7 100644 --- a/aioamqp/frame.py +++ b/aioamqp/frame.py @@ -79,7 +79,7 @@ async def read(reader): except (asyncio.IncompleteReadError, socket.error) as ex: raise exceptions.AmqpClosedConnection() from ex - frame_type, channel, frame_length = pamqp.frame._frame_parts(data) + frame_type, channel, frame_length = pamqp.frame.frame_parts(data) payload_data = await reader.readexactly(frame_length) frame = None diff --git a/docs/changelog.rst b/docs/changelog.rst index 72f64f8..bce73af 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -8,6 +8,7 @@ Next release * Uses pamqp to encode or decode protocol frames. * Drops support of python 3.3 and python 3.4. * Uses async and await keywords. + * Fix pamqp `_frame_parts` call, now uses exposed `frame_parts` Aioamqp 0.12.0 -------------- diff --git a/setup.py b/setup.py index 1ec3eae..d4becb4 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ 'aioamqp', ], install_requires=[ - 'pamqp>=2.0,<3', + 'pamqp>=2.2.0,<3', ], classifiers=[ "Development Status :: 4 - Beta", From c175f11fe0e3132d0eafa70cf4364c0985e9a939 Mon Sep 17 00:00:00 2001 From: Benoit Calvez Date: Thu, 9 May 2019 09:48:00 +0200 Subject: [PATCH 60/72] Release version v0.13.0 --- aioamqp/version.py | 2 +- docs/changelog.rst | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/aioamqp/version.py b/aioamqp/version.py index 412a111..da45e52 100644 --- a/aioamqp/version.py +++ b/aioamqp/version.py @@ -1,2 +1,2 @@ -__version__ = '0.12.0' +__version__ = '0.13.0' __packagename__ = 'aioamqp' diff --git a/docs/changelog.rst b/docs/changelog.rst index bce73af..96f0d81 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,9 @@ Changelog Next release ------------ +Aioamqp 0.13.0 +-------------- + * SSL Connections must be configured with an SSLContext object in ``connect`` and ``from_url`` (closes #142). * Uses pamqp to encode or decode protocol frames. * Drops support of python 3.3 and python 3.4. From fec27912ee3ac94cd5dcf57e8507b9f68e3f44f3 Mon Sep 17 00:00:00 2001 From: dzen Date: Mon, 20 May 2019 10:15:58 +0200 Subject: [PATCH 61/72] Rename message property `type` to `message_type` The ``Properties`` object is now compatible with pamqp's one. --- aioamqp/constants.py | 6 ++++-- aioamqp/properties.py | 6 +++--- docs/api.rst | 4 ++-- docs/changelog.rst | 2 ++ 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/aioamqp/constants.py b/aioamqp/constants.py index f35a552..5866bd6 100644 --- a/aioamqp/constants.py +++ b/aioamqp/constants.py @@ -92,8 +92,10 @@ CONFIRM_SELECT = 10 CONFIRM_SELECT_OK = 11 -MESSAGE_PROPERTIES = ('content_type', 'content_encoding', 'headers', 'delivery_mode', 'priority', 'correlation_id', - 'reply_to', 'expiration', 'message_id', 'timestamp', 'type', 'user_id', 'app_id', 'cluster_id') +MESSAGE_PROPERTIES = ( + 'content_type', 'content_encoding', 'headers', 'delivery_mode', 'priority', 'correlation_id', + 'reply_to', 'expiration', 'message_id', 'timestamp', 'message_type', 'user_id', 'app_id', 'cluster_id', +) FLAG_CONTENT_TYPE = (1 << 15) FLAG_CONTENT_ENCODING = (1 << 14) diff --git a/aioamqp/properties.py b/aioamqp/properties.py index 43ab642..56a3484 100644 --- a/aioamqp/properties.py +++ b/aioamqp/properties.py @@ -9,7 +9,7 @@ class Properties: def __init__( self, content_type=None, content_encoding=None, headers=None, delivery_mode=None, priority=None, correlation_id=None, reply_to=None, expiration=None, message_id=None, - timestamp=None, type=None, user_id=None, app_id=None, cluster_id=None): # pylint: disable=redefined-builtin + timestamp=None, message_type=None, user_id=None, app_id=None, cluster_id=None): self.content_type = content_type self.content_encoding = content_encoding self.headers = headers @@ -20,7 +20,7 @@ def __init__( self.expiration = expiration self.message_id = message_id self.timestamp = timestamp - self.type = type + self.message_type = message_type self.user_id = user_id self.app_id = app_id self.cluster_id = cluster_id @@ -38,7 +38,7 @@ def from_pamqp(instance): props.expiration = instance.expiration props.message_id = instance.message_id props.timestamp = instance.timestamp - props.type = instance.message_type + props.message_type = instance.message_type props.user_id = instance.user_id props.app_id = instance.app_id props.cluster_id = instance.cluster_id diff --git a/docs/api.rst b/docs/api.rst index e3c5a2f..a705434 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -159,7 +159,7 @@ In the callback: routing_key is_redeliver -* the ``properties`` are message properties, an instance of properties.Properties with the following members:: +* the ``properties`` are message properties, an instance of ``properties.Properties`` with the following members:: content_type content_encoding @@ -171,7 +171,7 @@ In the callback: expiration message_id timestamp - type + message_type user_id app_id cluster_id diff --git a/docs/changelog.rst b/docs/changelog.rst index 96f0d81..8d81876 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,8 @@ Changelog Next release ------------ + * Rename ``type`` to ``message_type`` in constant.Properties object to be full compatible with pamqp. + Aioamqp 0.13.0 -------------- From 3ea7ea263e04bac06d5963738a9b1265af148594 Mon Sep 17 00:00:00 2001 From: Nick Humrich Date: Wed, 21 Feb 2018 11:03:48 -0700 Subject: [PATCH 62/72] Fix waiter already exists issue Fixes #105 --- aioamqp/channel.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/aioamqp/channel.py b/aioamqp/channel.py index 97742ad..0fc83aa 100644 --- a/aioamqp/channel.py +++ b/aioamqp/channel.py @@ -134,7 +134,13 @@ async def _write_frame_awaiting_response(self, waiter_id, channel_id, request, self._get_waiter(waiter_id) f.cancel() raise - return (await f) + result = await f + try: + self._get_waiter(waiter_id) + except exceptions.SynchronizationError: + # no waiter to get + pass + return result # ## Channel class implementation From 0361842e7e87013f050dec90b03b74fc8bdd765d Mon Sep 17 00:00:00 2001 From: dzen Date: Mon, 15 Jul 2019 17:51:55 +0200 Subject: [PATCH 63/72] add changelog entry (#105) --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 8d81876..66e5a1d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,7 @@ Changelog Next release ------------ + * Fix ``waiter already exist`` issue when creating multiple queues (closes #105). * Rename ``type`` to ``message_type`` in constant.Properties object to be full compatible with pamqp. Aioamqp 0.13.0 From 3cbfdf5c5f7f003c0259bab3b5d5dee39838a161 Mon Sep 17 00:00:00 2001 From: dzen Date: Mon, 15 Jul 2019 18:01:32 +0200 Subject: [PATCH 64/72] Revert "Fix waiter already exists issue" This reverts commit 3ea7ea263e04bac06d5963738a9b1265af148594. --- aioamqp/channel.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/aioamqp/channel.py b/aioamqp/channel.py index 0fc83aa..97742ad 100644 --- a/aioamqp/channel.py +++ b/aioamqp/channel.py @@ -134,13 +134,7 @@ async def _write_frame_awaiting_response(self, waiter_id, channel_id, request, self._get_waiter(waiter_id) f.cancel() raise - result = await f - try: - self._get_waiter(waiter_id) - except exceptions.SynchronizationError: - # no waiter to get - pass - return result + return (await f) # ## Channel class implementation From acd871ac7f930892ad5994e07789b83d0cd4512b Mon Sep 17 00:00:00 2001 From: Nick Humrich Date: Fri, 1 Mar 2019 19:58:58 -0700 Subject: [PATCH 65/72] Fix waiter issues --- aioamqp/channel.py | 28 +++++++++++----------- aioamqp/tests/test_consume.py | 44 +++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/aioamqp/channel.py b/aioamqp/channel.py index 97742ad..7f0f402 100644 --- a/aioamqp/channel.py +++ b/aioamqp/channel.py @@ -37,6 +37,8 @@ def __init__(self, protocol, channel_id, return_callback=None): self.publisher_confirms = False self.delivery_tag_iter = None # used for mapping delivered messages to publisher confirms + self._exchange_declare_lock = asyncio.Lock() + self._queue_bind_lock = asyncio.Lock() self._futures = {} self._ctag_events = {} @@ -209,8 +211,9 @@ async def exchange_declare(self, exchange_name, type_name, passive=False, durabl arguments=arguments ) - return (await self._write_frame_awaiting_response( - 'exchange_declare', self.channel_id, request, no_wait)) + async with self._exchange_declare_lock: + return (await self._write_frame_awaiting_response( + 'exchange_declare', self.channel_id, request, no_wait)) async def exchange_declare_ok(self, frame): future = self._get_waiter('exchange_declare') @@ -220,8 +223,8 @@ async def exchange_declare_ok(self, frame): async def exchange_delete(self, exchange_name, if_unused=False, no_wait=False): request = pamqp.specification.Exchange.Delete(exchange=exchange_name, if_unused=if_unused, nowait=no_wait) - return (await self._write_frame_awaiting_response( - 'exchange_delete', self.channel_id, request, no_wait)) + return await self._write_frame_awaiting_response( + 'exchange_delete', self.channel_id, request, no_wait) async def exchange_delete_ok(self, frame): future = self._get_waiter('exchange_delete') @@ -293,7 +296,7 @@ async def queue_declare(self, queue_name=None, passive=False, durable=False, arguments = {} if not queue_name: - queue_name = '' + queue_name = 'aioamqp.gen-' + str(uuid.uuid4()) request = pamqp.specification.Queue.Declare( queue=queue_name, passive=passive, @@ -304,7 +307,7 @@ async def queue_declare(self, queue_name=None, passive=False, durable=False, arguments=arguments ) return (await self._write_frame_awaiting_response( - 'queue_declare', self.channel_id, request, no_wait)) + 'queue_declare' + queue_name, self.channel_id, request, no_wait)) async def queue_declare_ok(self, frame): results = { @@ -312,11 +315,10 @@ async def queue_declare_ok(self, frame): 'message_count': frame.message_count, 'consumer_count': frame.consumer_count, } - future = self._get_waiter('queue_declare') + future = self._get_waiter('queue_declare' + results['queue']) future.set_result(results) logger.debug("Queue declared") - async def queue_delete(self, queue_name, if_unused=False, if_empty=False, no_wait=False): """Delete a queue in RabbitMQ Args: @@ -352,8 +354,9 @@ async def queue_bind(self, queue_name, exchange_name, routing_key, no_wait=False arguments=arguments ) # short reserved-1 - return (await self._write_frame_awaiting_response( - 'queue_bind', self.channel_id, request, no_wait)) + async with self._queue_bind_lock: + return (await self._write_frame_awaiting_response( + 'queue_bind', self.channel_id, request, no_wait)) async def queue_bind_ok(self, frame): future = self._get_waiter('queue_bind') @@ -501,7 +504,7 @@ async def basic_consume(self, callback, queue_name='', consumer_tag='', no_local self.last_consumer_tag = consumer_tag return_value = await self._write_frame_awaiting_response( - 'basic_consume', self.channel_id, request, no_wait) + 'basic_consume' + consumer_tag, self.channel_id, request, no_wait) if no_wait: return_value = {'consumer_tag': consumer_tag} else: @@ -513,7 +516,7 @@ async def basic_consume_ok(self, frame): results = { 'consumer_tag': ctag, } - future = self._get_waiter('basic_consume') + future = self._get_waiter('basic_consume' + ctag) future.set_result(results) self._ctag_events[ctag] = asyncio.Event(loop=self._loop) @@ -610,7 +613,6 @@ async def basic_client_nack(self, delivery_tag, multiple=False, requeue=True): request = pamqp.specification.Basic.Nack(delivery_tag, multiple, requeue) await self._write_frame(self.channel_id, request) - async def basic_server_ack(self, frame): delivery_tag = frame.delivery_tag fut = self._get_waiter('basic_server_ack_{}'.format(delivery_tag)) diff --git a/aioamqp/tests/test_consume.py b/aioamqp/tests/test_consume.py index 2b587bb..52708eb 100644 --- a/aioamqp/tests/test_consume.py +++ b/aioamqp/tests/test_consume.py @@ -144,3 +144,47 @@ async def callback(channel, body, envelope, properties): await channel.basic_consume(callback, queue_name="q") sync_future.set_result(True) + + async def test_consume_multiple_queues_using_gather(self): + await asyncio.gather(self.channel.queue_declare("q1", exclusive=True, no_wait=False), + self.channel.queue_declare("q2", exclusive=True, no_wait=False)) + await asyncio.gather(self.channel.exchange_declare("e", "direct"), + self.channel.exchange_declare("f", "direct")) + await asyncio.gather(self.channel.queue_bind("q1", "e", routing_key="q1"), + self.channel.queue_bind("q2", "e", routing_key="q2")) + + # get a different channel + channel = await self.create_channel() + + q1_future = asyncio.Future(loop=self.loop) + + async def q1_callback(channel, body, envelope, properties): + q1_future.set_result((body, envelope, properties)) + + q2_future = asyncio.Future(loop=self.loop) + + async def q2_callback(channel, body, envelope, properties): + q2_future.set_result((body, envelope, properties)) + + # start consumers + results = await asyncio.gather(channel.basic_consume(q1_callback, queue_name="q1"), + channel.basic_consume(q2_callback, queue_name="q2")) + ctag_q1 = results[0]['consumer_tag'] + ctag_q2 = results[1]['consumer_tag'] + + # put message in q1 and q2 + await asyncio.gather(channel.publish("coucou1", "e", "q1"), + channel.publish("coucou2", "e", "q2")) + + # get it + body1, envelope1, properties1 = await q1_future + self.assertEqual(ctag_q1, envelope1.consumer_tag) + self.assertIsNotNone(envelope1.delivery_tag) + self.assertEqual(b"coucou1", body1) + self.assertIsInstance(properties1, Properties) + + # get it + body2, envelope2, properties2 = await q2_future + self.assertEqual(ctag_q2, envelope2.consumer_tag) + self.assertEqual(b"coucou2", body2) + self.assertIsInstance(properties2, Properties) From 3212949aaccfa9a570afd0aff6a2c9a50f7b5740 Mon Sep 17 00:00:00 2001 From: dzen Date: Thu, 22 Aug 2019 13:05:04 +0200 Subject: [PATCH 66/72] travis: fix rabbitmq installation. starts management API --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index b915d0c..c52f7a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,12 @@ install: - pip install -r requirements_dev.txt - pip install --editable . - pip freeze + +addons: + apt: + packages: + - rabbitmq-server +before_script: "sudo rabbitmq-plugins enable rabbitmq_management" script: "make test && make pylint" notifications: email: true From b4cda3f06a8c9fecb273d7dc82d794c564d9900e Mon Sep 17 00:00:00 2001 From: user <40304161+fullylegit@users.noreply.github.com> Date: Fri, 15 Nov 2019 18:22:21 +1100 Subject: [PATCH 67/72] Python 3.8 support In python 3.8 the StreamReader within the StreamReaderProtocol has been changed to a weakref. We have to keep a strong ref to the StreamReader in scope for the lifetime of the AmqpProtocol. Polyconseil/aioamqp#210 - Added python 3.8 to the travis, tox and setup files --- .travis.yml | 1 + aioamqp/protocol.py | 3 ++- setup.cfg | 2 +- setup.py | 1 + tox.ini | 2 +- 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c52f7a1..bc8003d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ python: - 3.5 - 3.6 - 3.7-dev +- 3.8 services: - rabbitmq env: diff --git a/aioamqp/protocol.py b/aioamqp/protocol.py index 9ee1ce3..ddf1285 100644 --- a/aioamqp/protocol.py +++ b/aioamqp/protocol.py @@ -65,7 +65,8 @@ def __init__(self, *args, **kwargs): client_properties: dict, client-props to tune the client identification """ self._loop = kwargs.get('loop') or asyncio.get_event_loop() - super().__init__(asyncio.StreamReader(loop=self._loop), loop=self._loop) + self._reader = asyncio.StreamReader(loop=self._loop) + super().__init__(self._reader, loop=self._loop) self._on_error_callback = kwargs.get('on_error') self.client_properties = kwargs.get('client_properties', {}) diff --git a/setup.cfg b/setup.cfg index 9c7463e..0ab7d0b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ [bdist_wheel] -python-tag = py35.py36.py37 +python-tag = py35.py36.py37.py38 diff --git a/setup.py b/setup.py index d4becb4..a740243 100644 --- a/setup.py +++ b/setup.py @@ -37,6 +37,7 @@ "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", ], platforms='all', license='BSD' diff --git a/tox.ini b/tox.ini index ee24073..127cc7b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35, py36, py37 +envlist = py35, py36, py37, py38 skipsdist = true skip_missing_interpreters = true From d5ee42264f23ab1226811e129f5248134c6f4127 Mon Sep 17 00:00:00 2001 From: user <40304161+fullylegit@users.noreply.github.com> Date: Sat, 16 Nov 2019 14:09:35 +1100 Subject: [PATCH 68/72] Fix broken test test_wrong_parameter_queue Changed the expected error code to be either 405 or 406 (depends on rabbitmq version) The test was broken due to a rabbitmq change: the order of the argument equality and queue exclusiveness checks has changed --- aioamqp/tests/test_queue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aioamqp/tests/test_queue.py b/aioamqp/tests/test_queue.py index f5ac996..be00e4e 100644 --- a/aioamqp/tests/test_queue.py +++ b/aioamqp/tests/test_queue.py @@ -72,7 +72,7 @@ async def test_wrong_parameter_queue(self): await self.channel.queue_declare(queue_name, passive=False, exclusive=True, auto_delete=True) - self.assertEqual(cm.exception.code, 406) + self.assertIn(cm.exception.code, [405, 406]) async def test_multiple_channel_same_queue(self): queue_name = 'queue_name' From faacec2aaff09ccd290ae8ae291ea58b8c7535ab Mon Sep 17 00:00:00 2001 From: user <40304161+fullylegit@users.noreply.github.com> Date: Sat, 16 Nov 2019 22:41:55 +1100 Subject: [PATCH 69/72] Fix the failing test in the travis build Changed travis to use bionic. The test test_queue_declare_custom_x_message_ttl_32_bits fails with rabbitmq 3.5.7 (latest for xenial) due to a bug parsing unsigned ints. The bug was fixed in 3.6.7 (bionic latest is 3.6.10). --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index bc8003d..1069e7c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: python +dist: bionic python: - 3.5 - 3.6 From b00d28b982b81fe7f3830bf1f13c3a13430013b9 Mon Sep 17 00:00:00 2001 From: dzen Date: Wed, 20 Nov 2019 10:30:59 +0100 Subject: [PATCH 70/72] add fullylegit to AUTHORS file --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 649d422..d62ca2f 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -24,3 +24,4 @@ AUTHORS are (and/or have been):: * Jacob Hagstedt P Suorra * Corey `notmeta` * Paul Wistrand + * fullylegit From 9f497de965314e1638b0843e64e423c9469486d0 Mon Sep 17 00:00:00 2001 From: dzen Date: Wed, 20 Nov 2019 10:31:55 +0100 Subject: [PATCH 71/72] update changelog --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 66e5a1d..df2b71e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,6 +6,7 @@ Next release * Fix ``waiter already exist`` issue when creating multiple queues (closes #105). * Rename ``type`` to ``message_type`` in constant.Properties object to be full compatible with pamqp. + * Add python 3.8 support. Aioamqp 0.13.0 -------------- From 54ade18e43d7da1e2613a3d05a51fb3d86311f41 Mon Sep 17 00:00:00 2001 From: dzen Date: Wed, 20 Nov 2019 10:32:43 +0100 Subject: [PATCH 72/72] Release version v0.14.0 --- aioamqp/version.py | 2 +- docs/changelog.rst | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/aioamqp/version.py b/aioamqp/version.py index da45e52..688e8d5 100644 --- a/aioamqp/version.py +++ b/aioamqp/version.py @@ -1,2 +1,2 @@ -__version__ = '0.13.0' +__version__ = '0.14.0' __packagename__ = 'aioamqp' diff --git a/docs/changelog.rst b/docs/changelog.rst index df2b71e..0939ca4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,9 @@ Changelog Next release ------------ +Aioamqp 0.14.0 +-------------- + * Fix ``waiter already exist`` issue when creating multiple queues (closes #105). * Rename ``type`` to ``message_type`` in constant.Properties object to be full compatible with pamqp. * Add python 3.8 support.