txAMQP-0.6.1/0000755000175000017500000000000011726126167012211 5ustar esteveestevetxAMQP-0.6.1/setup.cfg0000644000175000017500000000007311726126167014032 0ustar esteveesteve[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 txAMQP-0.6.1/PKG-INFO0000644000175000017500000000120511726126167013304 0ustar esteveesteveMetadata-Version: 1.0 Name: txAMQP Version: 0.6.1 Summary: Python library for communicating with AMQP peers and brokers using Twisted Home-page: https://launchpad.net/txamqp Author: Esteve Fernandez Author-email: esteve@fluidinfo.com License: UNKNOWN Description: This project contains all the necessary code to connect, send and receive messages to/from an AMQP-compliant peer or broker (Qpid, OpenAMQ, RabbitMQ) using Twisted. It also includes support for using Thrift RPC over AMQP in Twisted applications. txAMQP is sponsored by the friendly folks at Fluidinfo (http://www.fluidinfo.com). Platform: UNKNOWN txAMQP-0.6.1/src/0000755000175000017500000000000011726126167013000 5ustar esteveestevetxAMQP-0.6.1/src/txAMQP.egg-info/0000755000175000017500000000000011726126167015604 5ustar esteveestevetxAMQP-0.6.1/src/txAMQP.egg-info/SOURCES.txt0000644000175000017500000000170511726126167017473 0ustar esteveestevesetup.py src/txAMQP.egg-info/PKG-INFO src/txAMQP.egg-info/SOURCES.txt src/txAMQP.egg-info/dependency_links.txt src/txAMQP.egg-info/requires.txt src/txAMQP.egg-info/top_level.txt src/txamqp/__init__.py src/txamqp/client.py src/txamqp/codec.py src/txamqp/connection.py src/txamqp/content.py src/txamqp/delegate.py src/txamqp/message.py src/txamqp/protocol.py src/txamqp/queue.py src/txamqp/spec.py src/txamqp/testlib.py src/txamqp/xmlutil.py src/txamqp/contrib/__init__.py src/txamqp/contrib/thrift/__init__.py src/txamqp/contrib/thrift/client.py src/txamqp/contrib/thrift/protocol.py src/txamqp/contrib/thrift/service.py src/txamqp/contrib/thrift/transport.py src/txamqp/test/__init__.py src/txamqp/test/test_basic.py src/txamqp/test/test_broker.py src/txamqp/test/test_event.py src/txamqp/test/test_example.py src/txamqp/test/test_exchange.py src/txamqp/test/test_heartbeat.py src/txamqp/test/test_queue.py src/txamqp/test/test_testlib.py src/txamqp/test/test_tx.pytxAMQP-0.6.1/src/txAMQP.egg-info/requires.txt0000644000175000017500000000000711726126167020201 0ustar esteveesteveTwistedtxAMQP-0.6.1/src/txAMQP.egg-info/PKG-INFO0000644000175000017500000000120511726126167016677 0ustar esteveesteveMetadata-Version: 1.0 Name: txAMQP Version: 0.6.1 Summary: Python library for communicating with AMQP peers and brokers using Twisted Home-page: https://launchpad.net/txamqp Author: Esteve Fernandez Author-email: esteve@fluidinfo.com License: UNKNOWN Description: This project contains all the necessary code to connect, send and receive messages to/from an AMQP-compliant peer or broker (Qpid, OpenAMQ, RabbitMQ) using Twisted. It also includes support for using Thrift RPC over AMQP in Twisted applications. txAMQP is sponsored by the friendly folks at Fluidinfo (http://www.fluidinfo.com). Platform: UNKNOWN txAMQP-0.6.1/src/txAMQP.egg-info/dependency_links.txt0000644000175000017500000000000111726126167021652 0ustar esteveesteve txAMQP-0.6.1/src/txAMQP.egg-info/top_level.txt0000644000175000017500000000000711726126167020333 0ustar esteveestevetxamqp txAMQP-0.6.1/src/txamqp/0000755000175000017500000000000011726126167014312 5ustar esteveestevetxAMQP-0.6.1/src/txamqp/__init__.py0000644000175000017500000000000011525011461016374 0ustar esteveestevetxAMQP-0.6.1/src/txamqp/test/0000755000175000017500000000000011726126167015271 5ustar esteveestevetxAMQP-0.6.1/src/txamqp/test/test_broker.py0000644000175000017500000001270111525011461020152 0ustar esteveesteve# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # from txamqp.client import Closed from txamqp.queue import Empty from txamqp.content import Content from txamqp.testlib import TestBase, supportedBrokers, QPID, OPENAMQ from twisted.internet.defer import inlineCallbacks class BrokerTests(TestBase): """Tests for basic Broker functionality""" @inlineCallbacks def test_amqp_basic_13(self): """ First, this test tries to receive a message with a no-ack consumer. Second, this test tries to explicitely receive and acknowledge a message with an acknowledging consumer. """ ch = self.channel yield self.queue_declare(ch, queue = "myqueue") # No ack consumer ctag = (yield ch.basic_consume(queue = "myqueue", no_ack = True)).consumer_tag body = "test no-ack" ch.basic_publish(routing_key = "myqueue", content = Content(body)) msg = yield ((yield self.client.queue(ctag)).get(timeout = 5)) self.assert_(msg.content.body == body) # Acknowleding consumer yield self.queue_declare(ch, queue = "otherqueue") ctag = (yield ch.basic_consume(queue = "otherqueue", no_ack = False)).consumer_tag body = "test ack" ch.basic_publish(routing_key = "otherqueue", content = Content(body)) msg = yield ((yield self.client.queue(ctag)).get(timeout = 5)) ch.basic_ack(delivery_tag = msg.delivery_tag) self.assert_(msg.content.body == body) @inlineCallbacks def test_basic_delivery_immediate(self): """ Test basic message delivery where consume is issued before publish """ channel = self.channel yield self.exchange_declare(channel, exchange="test-exchange", type="direct") yield self.queue_declare(channel, queue="test-queue") yield channel.queue_bind(queue="test-queue", exchange="test-exchange", routing_key="key") reply = yield channel.basic_consume(queue="test-queue", no_ack=True) queue = yield self.client.queue(reply.consumer_tag) body = "Immediate Delivery" channel.basic_publish(exchange="test-exchange", routing_key="key", content=Content(body), immediate=True) msg = yield queue.get(timeout=5) self.assert_(msg.content.body == body) # TODO: Ensure we fail if immediate=True and there's no consumer. @inlineCallbacks def test_basic_delivery_queued(self): """ Test basic message delivery where publish is issued before consume (i.e. requires queueing of the message) """ channel = self.channel yield self.exchange_declare(channel, exchange="test-exchange", type="direct") yield self.queue_declare(channel, queue="test-queue") yield channel.queue_bind(queue="test-queue", exchange="test-exchange", routing_key="key") body = "Queued Delivery" channel.basic_publish(exchange="test-exchange", routing_key="key", content=Content(body)) reply = yield channel.basic_consume(queue="test-queue", no_ack=True) queue = yield self.client.queue(reply.consumer_tag) msg = yield queue.get(timeout=5) self.assert_(msg.content.body == body) @inlineCallbacks def test_invalid_channel(self): channel = yield self.client.channel(200) try: yield channel.queue_declare(exclusive=True) self.fail("Expected error on queue_declare for invalid channel") except Closed, e: self.assertConnectionException(504, e.args[0]) @inlineCallbacks def test_closed_channel(self): channel = yield self.client.channel(200) yield channel.channel_open() yield channel.channel_close() try: yield channel.queue_declare(exclusive=True) self.fail("Expected error on queue_declare for closed channel") except Closed, e: self.assertConnectionException(504, e.args[0]) @supportedBrokers(QPID, OPENAMQ) @inlineCallbacks def test_channel_flow(self): channel = self.channel yield channel.queue_declare(queue="flow_test_queue", exclusive=True) yield channel.basic_consume(consumer_tag="my-tag", queue="flow_test_queue") incoming = yield self.client.queue("my-tag") yield channel.channel_flow(active=False) channel.basic_publish(routing_key="flow_test_queue", content=Content("abcdefghijklmnopqrstuvwxyz")) try: yield incoming.get(timeout=1) self.fail("Received message when flow turned off.") except Empty: None yield channel.channel_flow(active=True) msg = yield incoming.get(timeout=1) self.assertEqual("abcdefghijklmnopqrstuvwxyz", msg.content.body) txAMQP-0.6.1/src/txamqp/test/__init__.py0000644000175000017500000000000011525011461017353 0ustar esteveestevetxAMQP-0.6.1/src/txamqp/test/test_example.py0000644000175000017500000000766611525011461020337 0ustar esteveesteve# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # from txamqp.content import Content from txamqp.testlib import TestBase from twisted.internet.defer import inlineCallbacks class ExampleTest(TestBase): """ An example Qpid test, illustrating the unittest frameowkr and the python Qpid client. The test class must inherit TestCase. The test code uses the Qpid client to interact with a qpid broker and verify it behaves as expected. """ @inlineCallbacks def test_example(self): """ An example test. Note that test functions must start with 'test_' to be recognized by the test framework. """ # By inheriting TestBase, self.client is automatically connected # and self.channel is automatically opened as channel(1) # Other channel methods mimic the protocol. channel = self.channel # Now we can send regular commands. If you want to see what the method # arguments mean or what other commands are available, you can use the # python builtin help() method. For example: #help(chan) #help(chan.exchange_declare) # If you want browse the available protocol methods without being # connected to a live server you can use the amqp-doc utility: # # Usage amqp-doc [] [ ... ] # # Options: # -e, --regexp use regex instead of glob when matching # Now that we know what commands are available we can use them to # interact with the server. # Here we use ordinal arguments. yield self.exchange_declare(channel, 0, "test", "direct") # Here we use keyword arguments. yield self.queue_declare(channel, queue="test-queue") yield channel.queue_bind(queue="test-queue", exchange="test", routing_key="key") # Call Channel.basic_consume to register as a consumer. # All the protocol methods return a message object. The message object # has fields corresponding to the reply method fields, plus a content # field that is filled if the reply includes content. In this case the # interesting field is the consumer_tag. reply = yield channel.basic_consume(queue="test-queue") # We can use the Client.queue(...) method to access the queue # corresponding to our consumer_tag. queue = yield self.client.queue(reply.consumer_tag) # Now lets publish a message and see if our consumer gets it. To do # this we need to import the Content class. body = "Hello World!" channel.basic_publish(exchange="test", routing_key="key", content=Content(body)) # Now we'll wait for the message to arrive. We can use the timeout # argument in case the server hangs. By default queue.get() will wait # until a message arrives or the connection to the server dies. msg = yield queue.get(timeout=10) # And check that we got the right response with assertEqual self.assertEqual(body, msg.content.body) # Now acknowledge the message. channel.basic_ack(msg.delivery_tag, True) txAMQP-0.6.1/src/txamqp/test/test_exchange.py0000644000175000017500000003054511533227505020465 0ustar esteveesteve# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # """ Tests for exchange behaviour. Test classes ending in 'RuleTests' are derived from rules in amqp.xml. """ from txamqp.queue import Empty from txamqp.testlib import TestBase, supportedBrokers, QPID, OPENAMQ from txamqp.content import Content from txamqp.client import Closed from twisted.internet.defer import inlineCallbacks class StandardExchangeVerifier: """Verifies standard exchange behavior. Used as base class for classes that test standard exchanges.""" @inlineCallbacks def verifyDirectExchange(self, ex): """Verify that ex behaves like a direct exchange.""" yield self.queue_declare(queue="q") yield self.channel.queue_bind(queue="q", exchange=ex, routing_key="k") yield self.assertPublishConsume(exchange=ex, queue="q", routing_key="k") try: yield self.assertPublishConsume(exchange=ex, queue="q", routing_key="kk") self.fail("Expected Empty exception") except Empty: None # Expected @inlineCallbacks def verifyFanOutExchange(self, ex): """Verify that ex behaves like a fanout exchange.""" yield self.queue_declare(queue="q") yield self.channel.queue_bind(queue="q", exchange=ex) yield self.queue_declare(queue="p") yield self.channel.queue_bind(queue="p", exchange=ex) for qname in ["q", "p"]: yield self.assertPublishGet((yield self.consume(qname)), ex) @inlineCallbacks def verifyTopicExchange(self, ex): """Verify that ex behaves like a topic exchange""" yield self.queue_declare(queue="a") yield self.channel.queue_bind(queue="a", exchange=ex, routing_key="a.#.b.*") q = yield self.consume("a") yield self.assertPublishGet(q, ex, "a.b.x") yield self.assertPublishGet(q, ex, "a.x.b.x") yield self.assertPublishGet(q, ex, "a.x.x.b.x") # Shouldn't match self.channel.basic_publish(exchange=ex, routing_key="a.b") self.channel.basic_publish(exchange=ex, routing_key="a.b.x.y") self.channel.basic_publish(exchange=ex, routing_key="x.a.b.x") self.channel.basic_publish(exchange=ex, routing_key="a.b") yield self.assertEmpty(q) @inlineCallbacks def verifyHeadersExchange(self, ex): """Verify that ex is a headers exchange""" yield self.queue_declare(queue="q") yield self.channel.queue_bind(queue="q", exchange=ex, arguments={ "x-match":"all", "name":"fred" , "age":3} ) q = yield self.consume("q") headers = {"name":"fred", "age":3} yield self.assertPublishGet(q, exchange=ex, properties={'headers':headers}) self.channel.basic_publish(exchange=ex) # No headers, won't deliver yield self.assertEmpty(q); class RecommendedTypesRuleTests(TestBase, StandardExchangeVerifier): """ The server SHOULD implement these standard exchange types: topic, headers. Client attempts to declare an exchange with each of these standard types. """ @inlineCallbacks def testDirect(self): """Declare and test a direct exchange""" yield self.exchange_declare(0, exchange="d", type="direct") yield self.verifyDirectExchange("d") @inlineCallbacks def testFanout(self): """Declare and test a fanout exchange""" yield self.exchange_declare(0, exchange="f", type="fanout") yield self.verifyFanOutExchange("f") @inlineCallbacks def testTopic(self): """Declare and test a topic exchange""" yield self.exchange_declare(0, exchange="t", type="topic") yield self.verifyTopicExchange("t") @inlineCallbacks def testHeaders(self): """Declare and test a headers exchange""" yield self.exchange_declare(0, exchange="h", type="headers") yield self.verifyHeadersExchange("h") class RequiredInstancesRuleTests(TestBase, StandardExchangeVerifier): """ The server MUST, in each virtual host, pre-declare an exchange instance for each standard exchange type that it implements, where the name of the exchange instance is amq. followed by the exchange type name. Client creates a temporary queue and attempts to bind to each required exchange instance (amq.fanout, amq.direct, and amq.topic, amq.match if those types are defined). """ @inlineCallbacks def testAmqDirect(self): yield self.verifyDirectExchange("amq.direct") @inlineCallbacks def testAmqFanOut(self): yield self.verifyFanOutExchange("amq.fanout") @inlineCallbacks def testAmqTopic(self): yield self.verifyTopicExchange("amq.topic") @inlineCallbacks def testAmqMatch(self): yield self.verifyHeadersExchange("amq.match") class DefaultExchangeRuleTests(TestBase, StandardExchangeVerifier): """ The server MUST predeclare a direct exchange to act as the default exchange for content Publish methods and for default queue bindings. Client checks that the default exchange is active by specifying a queue binding with no exchange name, and publishing a message with a suitable routing key but without specifying the exchange name, then ensuring that the message arrives in the queue correctly. """ @supportedBrokers(QPID, OPENAMQ) @inlineCallbacks def testDefaultExchange(self): # Test automatic binding by queue name. yield self.queue_declare(queue="d") yield self.assertPublishConsume(queue="d", routing_key="d") # Test explicit bind to default queue yield self.verifyDirectExchange("") # TODO aconway 2006-09-27: Fill in empty tests: class DefaultAccessRuleTests(TestBase): """ The server MUST NOT allow clients to access the default exchange except by specifying an empty exchange name in the Queue.Bind and content Publish methods. """ class ExtensionsRuleTests(TestBase): """ The server MAY implement other exchange types as wanted. """ class DeclareMethodMinimumRuleTests(TestBase): """ The server SHOULD support a minimum of 16 exchanges per virtual host and ideally, impose no limit except as defined by available resources. The client creates as many exchanges as it can until the server reports an error; the number of exchanges successfuly created must be at least sixteen. """ class DeclareMethodTicketFieldValidityRuleTests(TestBase): """ The client MUST provide a valid access ticket giving "active" access to the realm in which the exchange exists or will be created, or "passive" access if the if-exists flag is set. Client creates access ticket with wrong access rights and attempts to use in this method. """ class DeclareMethodExchangeFieldReservedRuleTests(TestBase): """ Exchange names starting with "amq." are reserved for predeclared and standardised exchanges. The client MUST NOT attempt to create an exchange starting with "amq.". """ class DeclareMethodTypeFieldTypedRuleTests(TestBase): """ Exchanges cannot be redeclared with different types. The client MUST not attempt to redeclare an existing exchange with a different type than used in the original Exchange.Declare method. """ class DeclareMethodTypeFieldSupportRuleTests(TestBase): """ The client MUST NOT attempt to create an exchange with a type that the server does not support. """ class DeclareMethodPassiveFieldNotFoundRuleTests(TestBase): """ If set, and the exchange does not already exist, the server MUST raise a channel exception with reply code 404 (not found). """ @inlineCallbacks def test(self): try: yield self.channel.exchange_declare(exchange="humpty_dumpty", passive=True) self.fail("Expected 404 for passive declaration of unknown exchange.") except Closed, e: self.assertChannelException(404, e.args[0]) class DeclareMethodDurableFieldSupportRuleTests(TestBase): """ The server MUST support both durable and transient exchanges. """ class DeclareMethodDurableFieldStickyRuleTests(TestBase): """ The server MUST ignore the durable field if the exchange already exists. """ class DeclareMethodAutoDeleteFieldStickyRuleTests(TestBase): """ The server MUST ignore the auto-delete field if the exchange already exists. """ class DeleteMethodTicketFieldValidityRuleTests(TestBase): """ The client MUST provide a valid access ticket giving "active" access rights to the exchange's access realm. Client creates access ticket with wrong access rights and attempts to use in this method. """ class DeleteMethodExchangeFieldExistsRuleTests(TestBase): """ The client MUST NOT attempt to delete an exchange that does not exist. """ class HeadersExchangeTests(TestBase): """ Tests for headers exchange functionality. """ @inlineCallbacks def setUp(self): yield TestBase.setUp(self) yield self.queue_declare(queue="q") self.q = yield self.consume("q") @inlineCallbacks def myAssertPublishGet(self, headers): yield self.assertPublishGet(self.q, exchange="amq.match", properties={'headers':headers}) def myBasicPublish(self, headers): self.channel.basic_publish(exchange="amq.match", content=Content("foobar", properties={'headers':headers})) @inlineCallbacks def testMatchAll(self): yield self.channel.queue_bind(queue="q", exchange="amq.match", arguments={ 'x-match':'all', "name":"fred", "age":3}) yield self.myAssertPublishGet({"name":"fred", "age":3}) yield self.myAssertPublishGet({"name":"fred", "age":3, "extra":"ignoreme"}) # None of these should match self.myBasicPublish({}) self.myBasicPublish({"name":"barney"}) self.myBasicPublish({"name":10}) self.myBasicPublish({"name":"fred", "age":2}) yield self.assertEmpty(self.q) @inlineCallbacks def testMatchAny(self): yield self.channel.queue_bind(queue="q", exchange="amq.match", arguments={ 'x-match':'any', "name":"fred", "age":3}) yield self.myAssertPublishGet({"name":"fred"}) yield self.myAssertPublishGet({"name":"fred", "ignoreme":10}) yield self.myAssertPublishGet({"ignoreme":10, "age":3}) # Wont match self.myBasicPublish({}) self.myBasicPublish({"irrelevant":0}) yield self.assertEmpty(self.q) class MiscellaneousErrorsTests(TestBase): """ Test some miscellaneous error conditions """ @inlineCallbacks def testTypeNotKnown(self): try: yield self.channel.exchange_declare(exchange="test_type_not_known_exchange", type="invalid_type") self.fail("Expected 503 for declaration of unknown exchange type.") except Closed, e: self.assertConnectionException(503, e.args[0]) @supportedBrokers(QPID, OPENAMQ) @inlineCallbacks def testDifferentDeclaredType(self): yield self.channel.exchange_declare(exchange="test_different_declared_type_exchange", type="direct") try: yield self.channel.exchange_declare(exchange="test_different_declared_type_exchange", type="topic") self.fail("Expected 530 for redeclaration of exchange with different type.") except Closed, e: self.assertConnectionException(530, e.args[0]) #cleanup other = yield self.connect() c2 = yield other.channel(1) yield c2.channel_open() yield c2.exchange_delete(exchange="test_different_declared_type_exchange") txAMQP-0.6.1/src/txamqp/test/test_basic.py0000644000175000017500000004464311533227505017770 0ustar esteveesteve# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # from txamqp.client import Closed from txamqp.queue import Empty from txamqp.content import Content from txamqp.testlib import TestBase, supportedBrokers, QPID, OPENAMQ from twisted.internet.defer import inlineCallbacks class BasicTests(TestBase): """Tests for 'methods' on the amqp basic 'class'""" @supportedBrokers(QPID, OPENAMQ) @inlineCallbacks def test_consume_no_local(self): """ Test that the no_local flag is honoured in the consume method """ channel = self.channel #setup, declare two queues: yield channel.queue_declare(queue="test-queue-1a", exclusive=True) yield channel.queue_declare(queue="test-queue-1b", exclusive=True) #establish two consumers one of which excludes delivery of locally sent messages yield channel.basic_consume(consumer_tag="local_included", queue="test-queue-1a") yield channel.basic_consume(consumer_tag="local_excluded", queue="test-queue-1b", no_local=True) #send a message channel.basic_publish(routing_key="test-queue-1a", content=Content("consume_no_local")) channel.basic_publish(routing_key="test-queue-1b", content=Content("consume_no_local")) #check the queues of the two consumers excluded = yield self.client.queue("local_excluded") included = yield self.client.queue("local_included") msg = yield included.get() self.assertEqual("consume_no_local", msg.content.body) try: yield excluded.get(timeout=1) self.fail("Received locally published message though no_local=true") except Empty: None @inlineCallbacks def test_consume_exclusive(self): """ Test that the exclusive flag is honoured in the consume method """ channel = self.channel #setup, declare a queue: yield channel.queue_declare(queue="test-queue-2", exclusive=True) #check that an exclusive consumer prevents other consumer being created: yield channel.basic_consume(consumer_tag="first", queue="test-queue-2", exclusive=True) try: yield channel.basic_consume(consumer_tag="second", queue="test-queue-2") self.fail("Expected consume request to fail due to previous exclusive consumer") except Closed, e: self.assertChannelException(403, e.args[0]) #open new channel and cleanup last consumer: channel = yield self.client.channel(2) yield channel.channel_open() #check that an exclusive consumer cannot be created if a consumer already exists: yield channel.basic_consume(consumer_tag="first", queue="test-queue-2") try: yield channel.basic_consume(consumer_tag="second", queue="test-queue-2", exclusive=True) self.fail("Expected exclusive consume request to fail due to previous consumer") except Closed, e: self.assertChannelException(403, e.args[0]) @inlineCallbacks def test_consume_queue_not_found(self): """ C{basic_consume} fails with a channel exception with a C{404} code when the specified queue doesn't exist. """ channel = self.channel try: #queue specified but doesn't exist: yield channel.basic_consume(queue="invalid-queue") self.fail("Expected failure when consuming from non-existent queue") except Closed, e: self.assertChannelException(404, e.args[0]) @supportedBrokers(QPID, OPENAMQ) @inlineCallbacks def test_consume_queue_unspecified(self): """ C{basic_consume} fails with a connection exception with a C{503} code when no queue is specified. """ channel = self.channel try: #queue not specified and none previously declared for channel: yield channel.basic_consume(queue="") self.fail("Expected failure when consuming from unspecified queue") except Closed, e: self.assertConnectionException(530, e.args[0]) @inlineCallbacks def test_consume_unique_consumers(self): """ Ensure unique consumer tags are enforced """ channel = self.channel #setup, declare a queue: yield channel.queue_declare(queue="test-queue-3", exclusive=True) #check that attempts to use duplicate tags are detected and prevented: yield channel.basic_consume(consumer_tag="first", queue="test-queue-3") try: yield channel.basic_consume(consumer_tag="first", queue="test-queue-3") self.fail("Expected consume request to fail due to non-unique tag") except Closed, e: self.assertConnectionException(530, e.args[0]) @inlineCallbacks def test_cancel(self): """ Test compliance of the basic.cancel method """ channel = self.channel #setup, declare a queue: yield channel.queue_declare(queue="test-queue-4", exclusive=True) yield channel.basic_consume(consumer_tag="my-consumer", queue="test-queue-4") channel.basic_publish(routing_key="test-queue-4", content=Content("One")) #cancel should stop messages being delivered yield channel.basic_cancel(consumer_tag="my-consumer") channel.basic_publish(routing_key="test-queue-4", content=Content("Two")) myqueue = yield self.client.queue("my-consumer") msg = yield myqueue.get(timeout=1) self.assertEqual("One", msg.content.body) try: msg = yield myqueue.get(timeout=1) self.fail("Got message after cancellation: " + msg) except Empty: None #cancellation of non-existant consumers should be handled without error yield channel.basic_cancel(consumer_tag="my-consumer") yield channel.basic_cancel(consumer_tag="this-never-existed") @supportedBrokers(QPID, OPENAMQ) @inlineCallbacks def test_ack(self): """ Test basic ack/recover behaviour """ channel = self.channel yield channel.queue_declare(queue="test-ack-queue", exclusive=True) reply = yield channel.basic_consume(queue="test-ack-queue", no_ack=False) queue = yield self.client.queue(reply.consumer_tag) channel.basic_publish(routing_key="test-ack-queue", content=Content("One")) channel.basic_publish(routing_key="test-ack-queue", content=Content("Two")) channel.basic_publish(routing_key="test-ack-queue", content=Content("Three")) channel.basic_publish(routing_key="test-ack-queue", content=Content("Four")) channel.basic_publish(routing_key="test-ack-queue", content=Content("Five")) msg1 = yield queue.get(timeout=1) msg2 = yield queue.get(timeout=1) msg3 = yield queue.get(timeout=1) msg4 = yield queue.get(timeout=1) msg5 = yield queue.get(timeout=1) self.assertEqual("One", msg1.content.body) self.assertEqual("Two", msg2.content.body) self.assertEqual("Three", msg3.content.body) self.assertEqual("Four", msg4.content.body) self.assertEqual("Five", msg5.content.body) channel.basic_ack(delivery_tag=msg2.delivery_tag, multiple=True) #One & Two channel.basic_ack(delivery_tag=msg4.delivery_tag, multiple=False) #Four yield channel.basic_recover(requeue=False) msg3b = yield queue.get(timeout=1) msg5b = yield queue.get(timeout=1) self.assertEqual("Three", msg3b.content.body) self.assertEqual("Five", msg5b.content.body) try: extra = yield queue.get(timeout=1) self.fail("Got unexpected message: " + extra.content.body) except Empty: None @inlineCallbacks def test_recover_requeue(self): """ Test requeing on recovery """ channel = self.channel yield channel.queue_declare(queue="test-requeue", exclusive=True) subscription = yield channel.basic_consume(queue="test-requeue", no_ack=False) queue = yield self.client.queue(subscription.consumer_tag) channel.basic_publish(routing_key="test-requeue", content=Content("One")) channel.basic_publish(routing_key="test-requeue", content=Content("Two")) channel.basic_publish(routing_key="test-requeue", content=Content("Three")) channel.basic_publish(routing_key="test-requeue", content=Content("Four")) channel.basic_publish(routing_key="test-requeue", content=Content("Five")) msg1 = yield queue.get(timeout=1) msg2 = yield queue.get(timeout=1) msg3 = yield queue.get(timeout=1) msg4 = yield queue.get(timeout=1) msg5 = yield queue.get(timeout=1) self.assertEqual("One", msg1.content.body) self.assertEqual("Two", msg2.content.body) self.assertEqual("Three", msg3.content.body) self.assertEqual("Four", msg4.content.body) self.assertEqual("Five", msg5.content.body) channel.basic_ack(delivery_tag=msg2.delivery_tag, multiple=True) #One & Two channel.basic_ack(delivery_tag=msg4.delivery_tag, multiple=False) #Four yield channel.basic_cancel(consumer_tag=subscription.consumer_tag) subscription2 = yield channel.basic_consume(queue="test-requeue") queue2 = yield self.client.queue(subscription2.consumer_tag) yield channel.basic_recover(requeue=True) msg3b = yield queue2.get() msg5b = yield queue2.get() self.assertEqual("Three", msg3b.content.body) self.assertEqual("Five", msg5b.content.body) self.assertEqual(True, msg3b.redelivered) self.assertEqual(True, msg5b.redelivered) try: extra = yield queue2.get(timeout=1) self.fail("Got unexpected message in second queue: " + extra.content.body) except Empty: None try: extra = yield queue.get(timeout=1) self.fail("Got unexpected message in original queue: " + extra.content.body) except Empty: None @inlineCallbacks def test_qos_prefetch_count(self): """ Test that the prefetch count specified is honoured """ #setup: declare queue and subscribe channel = self.channel yield channel.queue_declare(queue="test-prefetch-count", exclusive=True) subscription = yield channel.basic_consume(queue="test-prefetch-count", no_ack=False) queue = yield self.client.queue(subscription.consumer_tag) #set prefetch to 5: yield channel.basic_qos(prefetch_count=5) #publish 10 messages: for i in range(1, 11): channel.basic_publish(routing_key="test-prefetch-count", content=Content("Message %d" % i)) #only 5 messages should have been delivered: for i in range(1, 6): msg = yield queue.get(timeout=1) self.assertEqual("Message %d" % i, msg.content.body) try: extra = yield queue.get(timeout=1) self.fail("Got unexpected 6th message in original queue: " + extra.content.body) except Empty: None #ack messages and check that the next set arrive ok: channel.basic_ack(delivery_tag=msg.delivery_tag, multiple=True) for i in range(6, 11): msg = yield queue.get(timeout=1) self.assertEqual("Message %d" % i, msg.content.body) channel.basic_ack(delivery_tag=msg.delivery_tag, multiple=True) try: extra = yield queue.get(timeout=1) self.fail("Got unexpected 11th message in original queue: " + extra.content.body) except Empty: None @supportedBrokers(QPID, OPENAMQ) @inlineCallbacks def test_qos_prefetch_size(self): """ Test that the prefetch size specified is honoured """ #setup: declare queue and subscribe channel = self.channel yield channel.queue_declare(queue="test-prefetch-size", exclusive=True) subscription = yield channel.basic_consume(queue="test-prefetch-size", no_ack=False) queue = yield self.client.queue(subscription.consumer_tag) #set prefetch to 50 bytes (each message is 9 or 10 bytes): channel.basic_qos(prefetch_size=50) #publish 10 messages: for i in range(1, 11): channel.basic_publish(routing_key="test-prefetch-size", content=Content("Message %d" % i)) #only 5 messages should have been delivered (i.e. 45 bytes worth): for i in range(1, 6): msg = yield queue.get(timeout=1) self.assertEqual("Message %d" % i, msg.content.body) try: extra = yield queue.get(timeout=1) self.fail("Got unexpected 6th message in original queue: " + extra.content.body) except Empty: None #ack messages and check that the next set arrive ok: channel.basic_ack(delivery_tag=msg.delivery_tag, multiple=True) for i in range(6, 11): msg = yield queue.get(timeout=1) self.assertEqual("Message %d" % i, msg.content.body) channel.basic_ack(delivery_tag=msg.delivery_tag, multiple=True) try: extra = yield queue.get(timeout=1) self.fail("Got unexpected 11th message in original queue: " + extra.content.body) except Empty: None #make sure that a single oversized message still gets delivered large = "abcdefghijklmnopqrstuvwxyz" large = large + "-" + large; channel.basic_publish(routing_key="test-prefetch-size", content=Content(large)) msg = yield queue.get(timeout=1) self.assertEqual(large, msg.content.body) @inlineCallbacks def test_get(self): """ Test basic_get method """ channel = self.channel yield channel.queue_declare(queue="test-get", exclusive=True) #publish some messages (no_ack=True) with persistent messaging for i in range(1, 11): msg=Content("Message %d" % i) msg["delivery mode"] = 2 channel.basic_publish(routing_key="test-get",content=msg ) #use basic_get to read back the messages, and check that we get an empty at the end for i in range(1, 11): reply = yield channel.basic_get(no_ack=True) self.assertEqual(reply.method.klass.name, "basic") self.assertEqual(reply.method.name, "get-ok") self.assertEqual("Message %d" % i, reply.content.body) reply = yield channel.basic_get(no_ack=True) self.assertEqual(reply.method.klass.name, "basic") self.assertEqual(reply.method.name, "get-empty") #publish some messages (no_ack=True) transient messaging for i in range(11, 21): channel.basic_publish(routing_key="test-get", content=Content("Message %d" % i)) #use basic_get to read back the messages, and check that we get an empty at the end for i in range(11, 21): reply = yield channel.basic_get(no_ack=True) self.assertEqual(reply.method.klass.name, "basic") self.assertEqual(reply.method.name, "get-ok") self.assertEqual("Message %d" % i, reply.content.body) reply = yield channel.basic_get(no_ack=True) self.assertEqual(reply.method.klass.name, "basic") self.assertEqual(reply.method.name, "get-empty") #repeat for no_ack=False #publish some messages (no_ack=False) with persistent messaging for i in range(21, 31): msg=Content("Message %d" % i) msg["delivery mode"] = 2 channel.basic_publish(routing_key="test-get",content=msg ) #use basic_get to read back the messages, and check that we get an empty at the end for i in range(21, 31): reply = yield channel.basic_get(no_ack=False) self.assertEqual(reply.method.klass.name, "basic") self.assertEqual(reply.method.name, "get-ok") self.assertEqual("Message %d" % i, reply.content.body) reply = yield channel.basic_get(no_ack=True) self.assertEqual(reply.method.klass.name, "basic") self.assertEqual(reply.method.name, "get-empty") #public some messages (no_ack=False) with transient messaging for i in range(31, 41): channel.basic_publish(routing_key="test-get", content=Content("Message %d" % i)) for i in range(31, 41): reply = yield channel.basic_get(no_ack=False) self.assertEqual(reply.method.klass.name, "basic") self.assertEqual(reply.method.name, "get-ok") self.assertEqual("Message %d" % i, reply.content.body) if(i == 33): channel.basic_ack(delivery_tag=reply.delivery_tag, multiple=True) if(i in [35, 37, 39]): channel.basic_ack(delivery_tag=reply.delivery_tag) reply = yield channel.basic_get(no_ack=True) self.assertEqual(reply.method.klass.name, "basic") self.assertEqual(reply.method.name, "get-empty") #recover(requeue=True) yield channel.basic_recover(requeue=True) #get the unacked messages again (34, 36, 38, 40) for i in [34, 36, 38, 40]: reply = yield channel.basic_get(no_ack=False) self.assertEqual(reply.method.klass.name, "basic") self.assertEqual(reply.method.name, "get-ok") self.assertEqual("Message %d" % i, reply.content.body) channel.basic_ack(delivery_tag=reply.delivery_tag) reply = yield channel.basic_get(no_ack=True) self.assertEqual(reply.method.klass.name, "basic") self.assertEqual(reply.method.name, "get-empty") yield channel.basic_recover(requeue=True) reply = yield channel.basic_get(no_ack=True) self.assertEqual(reply.method.klass.name, "basic") self.assertEqual(reply.method.name, "get-empty") txAMQP-0.6.1/src/txamqp/test/test_queue.py0000644000175000017500000002613111533227505020023 0ustar esteveesteve# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # from txamqp.client import Closed from txamqp.content import Content from txamqp.testlib import TestBase, supportedBrokers, QPID, OPENAMQ from twisted.internet.defer import inlineCallbacks class QueueTests(TestBase): """Tests for 'methods' on the amqp queue 'class'""" @supportedBrokers(QPID, OPENAMQ) @inlineCallbacks def test_purge(self): """ Test that the purge method removes messages from the queue """ channel = self.channel #setup, declare a queue and add some messages to it: yield channel.exchange_declare(exchange="test-exchange", type="direct") yield channel.queue_declare(queue="test-queue", exclusive=True) yield channel.queue_bind(queue="test-queue", exchange="test-exchange", routing_key="key") channel.basic_publish(exchange="test-exchange", routing_key="key", content=Content("one")) channel.basic_publish(exchange="test-exchange", routing_key="key", content=Content("two")) channel.basic_publish(exchange="test-exchange", routing_key="key", content=Content("three")) #check that the queue now reports 3 messages: reply = yield channel.queue_declare(queue="test-queue") self.assertEqual(3, reply.message_count) #now do the purge, then test that three messages are purged and the count drops to 0 reply = yield channel.queue_purge(queue="test-queue"); self.assertEqual(3, reply.message_count) reply = yield channel.queue_declare(queue="test-queue") self.assertEqual(0, reply.message_count) #send a further message and consume it, ensuring that the other messages are really gone channel.basic_publish(exchange="test-exchange", routing_key="key", content=Content("four")) reply = yield channel.basic_consume(queue="test-queue", no_ack=True) queue = yield self.client.queue(reply.consumer_tag) msg = yield queue.get(timeout=1) self.assertEqual("four", msg.content.body) #check error conditions (use new channels): channel = yield self.client.channel(2) yield channel.channel_open() try: #queue specified but doesn't exist: yield channel.queue_purge(queue="invalid-queue") self.fail("Expected failure when purging non-existent queue") except Closed, e: self.assertChannelException(404, e.args[0]) channel = yield self.client.channel(3) yield channel.channel_open() try: #queue not specified and none previously declared for channel: yield channel.queue_purge() self.fail("Expected failure when purging unspecified queue") except Closed, e: self.assertConnectionException(530, e.args[0]) #cleanup other = yield self.connect() channel = yield other.channel(1) yield channel.channel_open() yield channel.exchange_delete(exchange="test-exchange") @inlineCallbacks def test_declare_exclusive(self): """ Test that the exclusive field is honoured in queue.declare """ # TestBase.setUp has already opened channel(1) c1 = self.channel # Here we open a second separate connection: other = yield self.connect() c2 = yield other.channel(1) yield c2.channel_open() #declare an exclusive queue: yield c1.queue_declare(queue="exclusive-queue", exclusive="True") try: #other connection should not be allowed to declare this: yield c2.queue_declare(queue="exclusive-queue", exclusive="True") self.fail("Expected second exclusive queue_declare to raise a channel exception") except Closed, e: self.assertChannelException(405, e.args[0]) @inlineCallbacks def test_declare_passive(self): """ Test that the passive field is honoured in queue.declare """ channel = self.channel #declare an exclusive queue: yield channel.queue_declare(queue="passive-queue-1", exclusive="True") yield channel.queue_declare(queue="passive-queue-1", passive="True") try: #other connection should not be allowed to declare this: yield channel.queue_declare(queue="passive-queue-2", passive="True") self.fail("Expected passive declaration of non-existant queue to raise a channel exception") except Closed, e: self.assertChannelException(404, e.args[0]) @inlineCallbacks def test_bind(self): """ Test various permutations of the queue.bind method """ channel = self.channel yield channel.queue_declare(queue="queue-1", exclusive="True") #straightforward case, both exchange & queue exist so no errors expected: yield channel.queue_bind(queue="queue-1", exchange="amq.direct", routing_key="key1") #bind the default queue for the channel (i.e. last one declared): yield channel.queue_bind(exchange="amq.direct", routing_key="key2") #use the queue name where neither routing key nor queue are specified: yield channel.queue_bind(exchange="amq.direct") #try and bind to non-existant exchange try: yield channel.queue_bind(queue="queue-1", exchange="an-invalid-exchange", routing_key="key1") self.fail("Expected bind to non-existant exchange to fail") except Closed, e: self.assertChannelException(404, e.args[0]) #need to reopen a channel: channel = yield self.client.channel(2) yield channel.channel_open() #try and bind non-existant queue: try: yield channel.queue_bind(queue="queue-2", exchange="amq.direct", routing_key="key1") self.fail("Expected bind of non-existant queue to fail") except Closed, e: self.assertChannelException(404, e.args[0]) @inlineCallbacks def test_delete_simple(self): """ Test basic queue deletion """ channel = self.channel #straight-forward case: yield channel.queue_declare(queue="delete-me") channel.basic_publish(routing_key="delete-me", content=Content("a")) channel.basic_publish(routing_key="delete-me", content=Content("b")) channel.basic_publish(routing_key="delete-me", content=Content("c")) reply = yield channel.queue_delete(queue="delete-me") self.assertEqual(3, reply.message_count) #check that it has gone be declaring passively try: yield channel.queue_declare(queue="delete-me", passive="True") self.fail("Queue has not been deleted") except Closed, e: self.assertChannelException(404, e.args[0]) #check attempted deletion of non-existant queue is handled correctly: channel = yield self.client.channel(2) yield channel.channel_open() try: yield channel.queue_delete(queue="i-dont-exist", if_empty="True") self.fail("Expected delete of non-existant queue to fail") except Closed, e: self.assertChannelException(404, e.args[0]) @inlineCallbacks def test_delete_ifempty(self): """ Test that if_empty field of queue_delete is honoured """ channel = self.channel #create a queue and add a message to it (use default binding): yield channel.queue_declare(queue="delete-me-2") yield channel.queue_declare(queue="delete-me-2", passive="True") channel.basic_publish(routing_key="delete-me-2", content=Content("message")) #try to delete, but only if empty: try: yield channel.queue_delete(queue="delete-me-2", if_empty="True") self.fail("Expected delete if_empty to fail for non-empty queue") except Closed, e: self.assertChannelException(406, e.args[0]) #need new channel now: channel = yield self.client.channel(2) yield channel.channel_open() #empty queue: reply = yield channel.basic_consume(queue="delete-me-2", no_ack=True) queue = yield self.client.queue(reply.consumer_tag) msg = yield queue.get(timeout=1) self.assertEqual("message", msg.content.body) yield channel.basic_cancel(consumer_tag=reply.consumer_tag) #retry deletion on empty queue: yield channel.queue_delete(queue="delete-me-2", if_empty="True") #check that it has gone by declaring passively: try: yield channel.queue_declare(queue="delete-me-2", passive="True") self.fail("Queue has not been deleted") except Closed, e: self.assertChannelException(404, e.args[0]) @inlineCallbacks def test_delete_ifunused(self): """ Test that if_unused field of queue_delete is honoured """ channel = self.channel #create a queue and register a consumer: yield channel.queue_declare(queue="delete-me-3") yield channel.queue_declare(queue="delete-me-3", passive="True") reply = yield channel.basic_consume(queue="delete-me-3", no_ack=True) #need new channel now: channel2 = yield self.client.channel(2) yield channel2.channel_open() #try to delete, but only if empty: try: yield channel2.queue_delete(queue="delete-me-3", if_unused="True") self.fail("Expected delete if_unused to fail for queue with existing consumer") except Closed, e: self.assertChannelException(406, e.args[0]) yield channel.basic_cancel(consumer_tag=reply.consumer_tag) yield channel.queue_delete(queue="delete-me-3", if_unused="True") #check that it has gone by declaring passively: try: yield channel.queue_declare(queue="delete-me-3", passive="True") self.fail("Queue has not been deleted") except Closed, e: self.assertChannelException(404, e.args[0]) @inlineCallbacks def test_close_queue(self): from txamqp.queue import Closed as QueueClosed channel = self.channel reply = yield channel.queue_declare(queue="test-queue") reply = yield channel.basic_consume(queue="test-queue") queue = yield self.client.queue(reply.consumer_tag) d = self.assertFailure(queue.get(timeout=1), QueueClosed) self.client.close(None) yield d txAMQP-0.6.1/src/txamqp/test/test_testlib.py0000644000175000017500000000424011525011461020333 0ustar esteveesteve# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # # # Tests for the testlib itself. # from txamqp.testlib import TestBase from txamqp.queue import Empty from traceback import print_stack from twisted.internet.defer import inlineCallbacks def mytrace(frame, event, arg): print_stack(frame); print "====" return mytrace class TestBaseTest(TestBase): """Verify TestBase functions work as expected""" @inlineCallbacks def testAssertEmptyPass(self): """Test assert empty works""" yield self.queue_declare(queue="empty") q = yield self.consume("empty") yield self.assertEmpty(q) try: yield q.get(timeout=1) self.fail("Queue is not empty.") except Empty: None # Ignore @inlineCallbacks def testAssertEmptyFail(self): yield self.queue_declare(queue="full") q = yield self.consume("full") self.channel.basic_publish(routing_key="full") try: yield self.assertEmpty(q); self.fail("assertEmpty did not assert on non-empty queue") except AssertionError: None # Ignore @inlineCallbacks def testMessageProperties(self): """Verify properties are passed with message""" props={"headers":{"x":1, "y":2}} yield self.queue_declare(queue="q") q = yield self.consume("q") yield self.assertPublishGet(q, routing_key="q", properties=props) txAMQP-0.6.1/src/txamqp/test/test_tx.py0000644000175000017500000002013111525011461017315 0ustar esteveesteve# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # from txamqp.queue import Empty from txamqp.content import Content from txamqp.testlib import TestBase, supportedBrokers, QPID, OPENAMQ from twisted.internet.defer import inlineCallbacks, returnValue class TxTests(TestBase): """ Tests for 'methods' on the amqp tx 'class' """ @inlineCallbacks def test_commit(self): """ Test that commited publishes are delivered and commited acks are not re-delivered """ channel = self.channel queue_a, queue_b, queue_c = yield self.perform_txn_work(channel, "tx-commit-a", "tx-commit-b", "tx-commit-c") yield channel.tx_commit() #check results for i in range(1, 5): msg = yield queue_c.get(timeout=1) self.assertEqual("TxMessage %d" % i, msg.content.body) msg = yield queue_b.get(timeout=1) self.assertEqual("TxMessage 6", msg.content.body) msg = yield queue_a.get(timeout=1) self.assertEqual("TxMessage 7", msg.content.body) for q in [queue_a, queue_b, queue_c]: try: extra = yield q.get(timeout=1) self.fail("Got unexpected message: " + extra.content.body) except Empty: None #cleanup channel.basic_ack(delivery_tag=0, multiple=True) yield channel.tx_commit() @supportedBrokers(QPID, OPENAMQ) @inlineCallbacks def test_auto_rollback(self): """ Test that a channel closed with an open transaction is effectively rolled back """ channel = self.channel queue_a, queue_b, queue_c = yield self.perform_txn_work(channel, "tx-autorollback-a", "tx-autorollback-b", "tx-autorollback-c") for q in [queue_a, queue_b, queue_c]: try: extra = yield q.get(timeout=1) self.fail("Got unexpected message: " + extra.content.body) except Empty: None yield channel.tx_rollback() #check results for i in range(1, 5): msg = yield queue_a.get(timeout=1) self.assertEqual("Message %d" % i, msg.content.body) msg = yield queue_b.get(timeout=1) self.assertEqual("Message 6", msg.content.body) msg = yield queue_c.get(timeout=1) self.assertEqual("Message 7", msg.content.body) for q in [queue_a, queue_b, queue_c]: try: extra = yield q.get(timeout=1) self.fail("Got unexpected message: " + extra.content.body) except Empty: None #cleanup channel.basic_ack(delivery_tag=0, multiple=True) yield channel.tx_commit() @supportedBrokers(QPID, OPENAMQ) @inlineCallbacks def test_rollback(self): """ Test that rolled back publishes are not delivered and rolled back acks are re-delivered """ channel = self.channel queue_a, queue_b, queue_c = yield self.perform_txn_work(channel, "tx-rollback-a", "tx-rollback-b", "tx-rollback-c") for q in [queue_a, queue_b, queue_c]: try: extra = yield q.get(timeout=1) self.fail("Got unexpected message: " + extra.content.body) except Empty: None yield channel.tx_rollback() #check results for i in range(1, 5): msg = yield queue_a.get(timeout=1) self.assertEqual("Message %d" % i, msg.content.body) msg = yield queue_b.get(timeout=1) self.assertEqual("Message 6", msg.content.body) msg = yield queue_c.get(timeout=1) self.assertEqual("Message 7", msg.content.body) for q in [queue_a, queue_b, queue_c]: try: extra = yield q.get(timeout=1) self.fail("Got unexpected message: " + extra.content.body) except Empty: None #cleanup channel.basic_ack(delivery_tag=0, multiple=True) yield channel.tx_commit() @inlineCallbacks def perform_txn_work(self, channel, name_a, name_b, name_c): """ Utility method that does some setup and some work under a transaction. Used for testing both commit and rollback """ #setup: yield channel.queue_declare(queue=name_a, exclusive=True) yield channel.queue_declare(queue=name_b, exclusive=True) yield channel.queue_declare(queue=name_c, exclusive=True) key = "my_key_" + name_b topic = "my_topic_" + name_c yield channel.queue_bind(queue=name_b, exchange="amq.direct", routing_key=key) yield channel.queue_bind(queue=name_c, exchange="amq.topic", routing_key=topic) for i in range(1, 5): channel.basic_publish(routing_key=name_a, content=Content("Message %d" % i)) channel.basic_publish(routing_key=key, exchange="amq.direct", content=Content("Message 6")) channel.basic_publish(routing_key=topic, exchange="amq.topic", content=Content("Message 7")) yield channel.tx_select() #consume and ack messages sub_a = yield channel.basic_consume(queue=name_a, no_ack=False) queue_a = yield self.client.queue(sub_a.consumer_tag) for i in range(1, 5): msg = yield queue_a.get(timeout=1) self.assertEqual("Message %d" % i, msg.content.body) channel.basic_ack(delivery_tag=msg.delivery_tag, multiple=True) sub_b = yield channel.basic_consume(queue=name_b, no_ack=False) queue_b = yield self.client.queue(sub_b.consumer_tag) msg = yield queue_b.get(timeout=1) self.assertEqual("Message 6", msg.content.body) channel.basic_ack(delivery_tag=msg.delivery_tag) sub_c = yield channel.basic_consume(queue=name_c, no_ack=False) queue_c = yield self.client.queue(sub_c.consumer_tag) msg = yield queue_c.get(timeout=1) self.assertEqual("Message 7", msg.content.body) channel.basic_ack(delivery_tag=msg.delivery_tag) #publish messages for i in range(1, 5): channel.basic_publish(routing_key=topic, exchange="amq.topic", content=Content("TxMessage %d" % i)) channel.basic_publish(routing_key=key, exchange="amq.direct", content=Content("TxMessage 6")) channel.basic_publish(routing_key=name_a, content=Content("TxMessage 7")) returnValue((queue_a, queue_b, queue_c)) @inlineCallbacks def test_commit_overlapping_acks(self): """ Test that logically 'overlapping' acks do not cause errors on commit """ channel = self.channel yield channel.queue_declare(queue="commit-overlapping", exclusive=True) for i in range(1, 10): channel.basic_publish(routing_key="commit-overlapping", content=Content("Message %d" % i)) yield channel.tx_select() sub = yield channel.basic_consume(queue="commit-overlapping", no_ack=False) queue = yield self.client.queue(sub.consumer_tag) for i in range(1, 10): msg = yield queue.get(timeout=1) self.assertEqual("Message %d" % i, msg.content.body) if i in [3, 6, 10]: channel.basic_ack(delivery_tag=msg.delivery_tag) yield channel.tx_commit() #check all have been acked: try: extra = yield queue.get(timeout=1) self.fail("Got unexpected message: " + extra.content.body) except Empty: None txAMQP-0.6.1/src/txamqp/test/test_heartbeat.py0000644000175000017500000000216011525011461020623 0ustar esteveestevefrom txamqp.testlib import TestBase from txamqp.protocol import AMQClient from twisted.internet import reactor from twisted.internet.defer import Deferred class SpyAMQClient(AMQClient): called_reschedule_check = 0 called_send_hb = 0 def reschedule_checkHB(self, dummy=None): AMQClient.reschedule_checkHB(self) self.called_reschedule_check += 1 def sendHeartbeat(self): AMQClient.sendHeartbeat(self) self.called_send_hb += 1 class HeartbeatTests(TestBase): heartbeat = 1 clientClass = SpyAMQClient """ Tests handling of heartbeat frames """ def test_heartbeat(self): """ Test that heartbeat frames are sent and received """ d = Deferred() def checkPulse(dummy): self.assertTrue(self.client.called_send_hb, "A heartbeat frame was recently sent") self.assertTrue(self.client.called_reschedule_check, "A heartbeat frame was recently received") d.addCallback(checkPulse) reactor.callLater(3, d.callback, None) return d txAMQP-0.6.1/src/txamqp/test/test_event.py0000644000175000017500000000511411525011461020007 0ustar esteveesteve# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # from txamqp.client import TwistedEvent, AlreadyFiredError from twisted.trial import unittest from twisted.python.failure import Failure class EventTest(unittest.TestCase): def test_fire(self): """Test event success.""" result = [] def fired(res): result.append(res) e = TwistedEvent() e.wait().addCallback(fired) self.assertEqual(result, []) e.fire() self.assertEqual(result, [True]) self.assertRaises(AlreadyFiredError, e.fire) self.assertRaises(AlreadyFiredError, e.fail, None) e.wait().addCallback(fired) self.assertEqual(result, [True, True]) e.fail_if_not_fired(None) def test_fail(self): """Test event failure.""" result = [] def failed(res): result.append(res) e = TwistedEvent() e.wait().addErrback(failed) self.assertEqual(result, []) f = Failure(Exception()) e.fail(f) self.assertEqual(result, [f]) self.assertRaises(AlreadyFiredError, e.fire) self.assertRaises(AlreadyFiredError, e.fail, f) e.wait().addErrback(failed) self.assertEqual(result, [f, f]) e.fail_if_not_fired(None) e = TwistedEvent() e.wait().addErrback(failed) e.fail_if_not_fired(f) self.assertEqual(result, [f, f, f]) def test_independent(self): """Test that waiters are independent.""" result = [] def bad(res): result.append(res) raise Exception() def fired(res): result.append(res) e = TwistedEvent() d = e.wait().addCallback(bad) e.wait().addCallback(fired) d.addErrback(lambda _ : None) e.fire() self.assertEqual(result, [True, True]) txAMQP-0.6.1/src/txamqp/contrib/0000755000175000017500000000000011726126167015752 5ustar esteveestevetxAMQP-0.6.1/src/txamqp/contrib/__init__.py0000644000175000017500000000000011525011461020034 0ustar esteveestevetxAMQP-0.6.1/src/txamqp/contrib/thrift/0000755000175000017500000000000011726126167017252 5ustar esteveestevetxAMQP-0.6.1/src/txamqp/contrib/thrift/service.py0000644000175000017500000000037211525011461021251 0ustar esteveestevefrom zope.interface import Interface, Attribute class IThriftAMQClientFactory(Interface): iprot_factory = Attribute("Input protocol factory") oprot_factory = Attribute("Input protocol factory") processor = Attribute("Thrift processor") txAMQP-0.6.1/src/txamqp/contrib/thrift/__init__.py0000644000175000017500000000000011525011461021334 0ustar esteveestevetxAMQP-0.6.1/src/txamqp/contrib/thrift/client.py0000644000175000017500000000140211525011461021062 0ustar esteveestevefrom twisted.internet import defer from twisted.python import log from txamqp.client import TwistedDelegate class ThriftTwistedDelegate(TwistedDelegate): @defer.inlineCallbacks def basic_return_(self, ch, msg): try: headers = msg.content['headers'] except KeyError: log.msg("'headers' not in msg.content: %r" % msg.content) else: try: thriftClientName = headers['thriftClientName'] except KeyError: log.msg("'thriftClientName' not in msg.content['headers']: %r" % msg.content['headers']) else: (yield self.client.thriftBasicReturnQueue(thriftClientName))\ .put(msg) txAMQP-0.6.1/src/txamqp/contrib/thrift/transport.py0000644000175000017500000000223011525011461021640 0ustar esteveestevefrom txamqp.content import Content from thrift.transport import TTwisted class TwistedAMQPTransport(TTwisted.TMessageSenderTransport): def __init__(self, channel, exchange, routingKey, clientName=None, replyTo=None, replyToField=None): TTwisted.TMessageSenderTransport.__init__(self) self.channel = channel self.exchange = exchange self.routingKey = routingKey # clientName is the name of the client class we are trying to get # the message through to. We need to send it seeing as the message # may be unroutable and we need a basic return that will tell us # who we are trying to reach. self.clientName = clientName self.replyTo = replyTo self.replyToField = replyToField def sendMessage(self, message): content = Content(body=message) if self.clientName: content['headers'] = { 'thriftClientName' : self.clientName } if self.replyTo: content[self.replyToField] = self.replyTo self.channel.basic_publish(exchange=self.exchange, routing_key=self.routingKey, content=content, mandatory=True) txAMQP-0.6.1/src/txamqp/contrib/thrift/protocol.py0000644000175000017500000002064611525011461021460 0ustar esteveestevefrom zope.interface import Interface, Attribute from txamqp.protocol import AMQClient from txamqp.contrib.thrift.transport import TwistedAMQPTransport from txamqp.queue import TimeoutDeferredQueue, Closed from twisted.internet import defer from twisted.python import log from thrift.transport import TTransport class ThriftAMQClient(AMQClient): def __init__(self, *args, **kwargs): AMQClient.__init__(self, *args, **kwargs) if self.check_0_8(): self.replyToField = "reply to" else: self.replyToField = "reply-to" self.thriftBasicReturnQueueLock = defer.DeferredLock() self.thriftBasicReturnQueues = {} @defer.inlineCallbacks def thriftBasicReturnQueue(self, key): yield self.thriftBasicReturnQueueLock.acquire() try: try: q = self.thriftBasicReturnQueues[key] except KeyError: q = TimeoutDeferredQueue() self.thriftBasicReturnQueues[key] = q finally: self.thriftBasicReturnQueueLock.release() defer.returnValue(q) @defer.inlineCallbacks def createThriftClient(self, responsesExchange, serviceExchange, routingKey, clientClass, channel=1, responseQueue=None, iprot_factory=None, oprot_factory=None): channel = yield self.channel(channel) if responseQueue is None: reply = yield channel.queue_declare(exclusive=True, auto_delete=True) responseQueue = reply.queue yield channel.queue_bind(queue=responseQueue, exchange=responsesExchange, routing_key=responseQueue) reply = yield channel.basic_consume(queue=responseQueue) log.msg("Consuming messages on queue: %s" % responseQueue) thriftClientName = clientClass.__name__ + routingKey amqpTransport = TwistedAMQPTransport( channel, serviceExchange, routingKey, clientName=thriftClientName, replyTo=responseQueue, replyToField=self.replyToField) if iprot_factory is None: iprot_factory = self.factory.iprot_factory if oprot_factory is None: oprot_factory = self.factory.oprot_factory thriftClient = clientClass(amqpTransport, oprot_factory) queue = yield self.queue(reply.consumer_tag) d = queue.get() d.addCallback(self.parseClientMessage, channel, queue, thriftClient, iprot_factory=iprot_factory) d.addErrback(self.catchClosedClientQueue) d.addErrback(self.handleClientQueueError) basicReturnQueue = yield self.thriftBasicReturnQueue(thriftClientName) d = basicReturnQueue.get() d.addCallback(self.parseClientUnrouteableMessage, channel, basicReturnQueue, thriftClient, iprot_factory=iprot_factory) d.addErrback(self.catchClosedClientQueue) d.addErrback(self.handleClientQueueError) defer.returnValue(thriftClient) def parseClientMessage(self, msg, channel, queue, thriftClient, iprot_factory=None): deliveryTag = msg.delivery_tag tr = TTransport.TMemoryBuffer(msg.content.body) if iprot_factory is None: iprot = self.factory.iprot_factory.getProtocol(tr) else: iprot = iprot_factory.getProtocol(tr) (fname, mtype, rseqid) = iprot.readMessageBegin() if rseqid in thriftClient._reqs: # log.msg('Got reply: fname = %r, rseqid = %s, mtype = %r, routing key = %r, client = %r, msg.content.body = %r' % (fname, rseqid, mtype, msg.routing_key, thriftClient, msg.content.body)) pass else: log.msg('Missing rseqid! fname = %r, rseqid = %s, mtype = %r, routing key = %r, client = %r, msg.content.body = %r' % (fname, rseqid, mtype, msg.routing_key, thriftClient, msg.content.body)) method = getattr(thriftClient, 'recv_' + fname) method(iprot, mtype, rseqid) channel.basic_ack(deliveryTag, True) d = queue.get() d.addCallback(self.parseClientMessage, channel, queue, thriftClient, iprot_factory=iprot_factory) d.addErrback(self.catchClosedClientQueue) d.addErrback(self.handleClientQueueError) def parseClientUnrouteableMessage(self, msg, channel, queue, thriftClient, iprot_factory=None): tr = TTransport.TMemoryBuffer(msg.content.body) if iprot_factory is None: iprot = self.factory.iprot_factory.getProtocol(tr) else: iprot = iprot_factory.getProtocol(tr) (fname, mtype, rseqid) = iprot.readMessageBegin() # log.msg('Got unroutable. fname = %r, rseqid = %s, mtype = %r, routing key = %r, client = %r, msg.content.body = %r' % (fname, rseqid, mtype, msg.routing_key, thriftClient, msg.content.body)) try: d = thriftClient._reqs.pop(rseqid) except KeyError: # KeyError will occur if the remote Thrift method is oneway, # since there is no outstanding local request deferred for # oneway calls. pass else: d.errback(TTransport.TTransportException( type=TTransport.TTransportException.NOT_OPEN, message='Unrouteable message, routing key = %r calling function %r' % (msg.routing_key, fname))) d = queue.get() d.addCallback(self.parseClientUnrouteableMessage, channel, queue, thriftClient, iprot_factory=iprot_factory) d.addErrback(self.catchClosedClientQueue) d.addErrback(self.handleClientQueueError) def catchClosedClientQueue(self, failure): # The queue is closed. Catch the exception and cleanup as needed. failure.trap(Closed) self.handleClosedClientQueue(failure) def handleClientQueueError(self, failure): pass def handleClosedClientQueue(self, failure): pass @defer.inlineCallbacks def createThriftServer(self, responsesExchange, serviceExchange, routingKey, processor, serviceQueue, channel=1, iprot_factory=None, oprot_factory=None): channel = yield self.channel(channel) yield channel.channel_open() yield channel.exchange_declare(exchange=serviceExchange, type="direct") yield channel.queue_declare(queue=serviceQueue, auto_delete=True) yield channel.queue_bind(queue=serviceQueue, exchange=serviceExchange, routing_key=routingKey) reply = yield channel.basic_consume(queue=serviceQueue) queue = yield self.queue(reply.consumer_tag) d = queue.get() d.addCallback(self.parseServerMessage, channel, responsesExchange, queue, processor, iprot_factory=iprot_factory, oprot_factory=oprot_factory) d.addErrback(self.catchClosedServerQueue) d.addErrback(self.handleServerQueueError) def parseServerMessage(self, msg, channel, exchange, queue, processor, iprot_factory=None, oprot_factory=None): deliveryTag = msg.delivery_tag try: replyTo = msg.content[self.replyToField] except KeyError: replyTo = None tmi = TTransport.TMemoryBuffer(msg.content.body) tr = TwistedAMQPTransport(channel, exchange, replyTo) if iprot_factory is None: iprot = self.factory.iprot_factory.getProtocol(tmi) else: iprot = iprot_factory.getProtocol(tmi) if oprot_factory is None: oprot = self.factory.oprot_factory.getProtocol(tr) else: oprot = oprot_factory.getProtocol(tr) d = processor.process(iprot, oprot) channel.basic_ack(deliveryTag, True) d = queue.get() d.addCallback(self.parseServerMessage, channel, exchange, queue, processor, iprot_factory, oprot_factory) d.addErrback(self.catchClosedServerQueue) d.addErrback(self.handleServerQueueError) def catchClosedServerQueue(self, failure): # The queue is closed. Catch the exception and cleanup as needed. failure.trap(Closed) self.handleClosedServerQueue(failure) def handleServerQueueError(self, failure): pass def handleClosedServerQueue(self, failure): pass class IThriftAMQClientFactory(Interface): iprot_factory = Attribute("Input protocol factory") oprot_factory = Attribute("Input protocol factory") processor = Attribute("Thrift processor") txAMQP-0.6.1/src/txamqp/delegate.py0000644000175000017500000000305311525011461016422 0ustar esteveesteve# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # import inspect from spec import pythonize class Delegate(object): def __init__(self): self.handlers = {} self.invokers = {} # initialize all the mixins self.invoke_all("init") def invoke_all(self, meth, *args, **kwargs): for cls in inspect.getmro(self.__class__): if hasattr(cls, meth): getattr(cls, meth)(self, *args, **kwargs) def dispatch(self, channel, message): method = message.method try: handler = self.handlers[method] except KeyError: name = "%s_%s" % (pythonize(method.klass.name), pythonize(method.name)) handler = getattr(self, name) self.handlers[method] = handler return handler(channel, message) def close(self, reason): self.invoke_all("close", reason) txAMQP-0.6.1/src/txamqp/client.py0000644000175000017500000000610211726126004016127 0ustar esteveesteve# coding: utf-8 from twisted.internet import defer from txamqp.delegate import Delegate class Closed(Exception): pass class AlreadyFiredError(Exception): pass class TwistedEvent(object): """ An asynchronous event that is in one of three states: 1. Not fired 2. Fired succesfully 3. Failed Clients wishing to be notified when the event has either occurred or failed can call wait() to receive a deferred that will be fired with callback(True) for state 2 and with errback(reason) for state 3. Each waiter gets an independent deferred that is not affected by other waiters. """ def __init__(self): self._waiters = [] # [Deferred] self._result = None # or ('callback'|'errback', result) def fire(self): """ Fire the event as successful. If the event was already fired, raise AlreadyFiredError. """ self._fire(('callback', True)) def fail(self, reason): """ Fire the event as failed with the given reason. If the event was already fired, raise AlreadyFiredError. """ self._fire(('errback', reason)) def fail_if_not_fired(self, reason): """ Fire the event as failed if it has not been fired. Otherwise do nothing. """ if self._result is None: self.fail(reason) def wait(self): """ Return a deferred that will be fired when the event is fired. """ d = defer.Deferred() if self._result is None: self._waiters.append(d) else: self._fire_deferred(d) return d def _fire(self, result): if self._result is not None: raise AlreadyFiredError() self._result = result waiters, self._waiters = self._waiters, [] for w in waiters: self._fire_deferred(w) def _fire_deferred(self, d): getattr(d, self._result[0])(self._result[1]) class TwistedDelegate(Delegate): def connection_start(self, ch, msg): ch.connection_start_ok(mechanism=self.client.mechanism, response=self.client.response, locale=self.client.locale) def connection_tune(self, ch, msg): self.client.MAX_LENGTH = msg.frame_max args = msg.channel_max, msg.frame_max, self.client.heartbeatInterval ch.connection_tune_ok(*args) self.client.started.fire() @defer.inlineCallbacks def basic_deliver(self, ch, msg): (yield self.client.queue(msg.consumer_tag)).put(msg) def basic_return_(self, ch, msg): self.client.basic_return_queue.put(msg) def channel_flow(self, ch, msg): ch.channel_flow_ok(active=msg.active) def channel_close(self, ch, msg): ch.channel_close_ok() ch.close(msg) def connection_close(self, ch, msg): self.client.close(msg) def close(self, reason): self.client.closed = True self.client.started.fail_if_not_fired(Closed(reason)) self.client.transport.loseConnection() txAMQP-0.6.1/src/txamqp/connection.py0000644000175000017500000001337111725126206017021 0ustar esteveesteve# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # import codec from cStringIO import StringIO from twisted.python import log from spec import pythonize class Frame(object): METHOD = "frame_method" HEADER = "frame_header" BODY = "frame_body" OOB_METHOD = "frame_oob_method" OOB_HEADER = "frame_oob_header" OOB_BODY = "frame_oob_body" TRACE = "frame_trace" HEARTBEAT = "frame_heartbeat" DECODERS = {} def __init__(self, channel, payload): self.channel = channel self.payload = payload def __str__(self): return "[%d] %s" % (self.channel, self.payload) class Payload(object): class __metaclass__(type): def __new__(cls, name, bases, dict): for req in ("encode", "decode", "type"): if not dict.has_key(req): raise TypeError("%s must define %s" % (name, req)) dict["decode"] = staticmethod(dict["decode"]) t = type.__new__(cls, name, bases, dict) if t.type != None: Frame.DECODERS[t.type] = t return t type = None def encode(self, enc): raise NotImplementedError def decode(spec, dec): raise NotImplementedError class Method(Payload): type = Frame.METHOD def __init__(self, method, *args): if len(args) != len(method.fields): argspec = ["%s: %s" % (pythonize(f.name), f.type) for f in method.fields] raise TypeError("%s.%s expecting (%s), got %s" % (pythonize(method.klass.name), pythonize(method.name), ", ".join(argspec), args)) self.method = method self.args = args def encode(self, enc): buf = StringIO() c = codec.Codec(buf) c.encode_short(self.method.klass.id) c.encode_short(self.method.id) for field, arg in zip(self.method.fields, self.args): c.encode(field.type, arg) c.flush() enc.encode_longstr(buf.getvalue()) def decode(spec, dec): enc = dec.decode_longstr() c = codec.Codec(StringIO(enc)) klass = spec.classes.byid[c.decode_short()] meth = klass.methods.byid[c.decode_short()] args = tuple([c.decode(f.type) for f in meth.fields]) return Method(meth, *args) def __str__(self): return "%s %s" % (self.method, ", ".join([str(a) for a in self.args])) class Header(Payload): type = Frame.HEADER def __init__(self, klass, weight, size, **properties): self.klass = klass self.weight = weight self.size = size self.properties = properties def __getitem__(self, name): return self.properties[name] def __setitem__(self, name, value): self.properties[name] = value def __delitem__(self, name): del self.properties[name] def encode(self, enc): buf = StringIO() c = codec.Codec(buf) c.encode_short(self.klass.id) c.encode_short(self.weight) c.encode_longlong(self.size) # property flags nprops = len(self.klass.fields) flags = 0 for i in range(nprops): f = self.klass.fields.items[i] flags <<= 1 if self.properties.get(f.name) != None: flags |= 1 # the last bit indicates more flags if i > 0 and (i % 15) == 0: flags <<= 1 if nprops > (i + 1): flags |= 1 c.encode_short(flags) flags = 0 flags <<= ((16 - (nprops % 15)) % 16) c.encode_short(flags) # properties for f in self.klass.fields: v = self.properties.get(f.name) if v != None: c.encode(f.type, v) unknown_props = set(self.properties.keys()) - \ set([f.name for f in self.klass.fields]) if unknown_props: log.msg("Unknown message properties: %s" % ", ".join(unknown_props)) c.flush() enc.encode_longstr(buf.getvalue()) def decode(spec, dec): c = codec.Codec(StringIO(dec.decode_longstr())) klass = spec.classes.byid[c.decode_short()] weight = c.decode_short() size = c.decode_longlong() # property flags bits = [] while True: flags = c.decode_short() for i in range(15, 0, -1): if flags >> i & 0x1 != 0: bits.append(True) else: bits.append(False) if flags & 0x1 == 0: break # properties properties = {} for b, f in zip(bits, klass.fields): if b: # Note: decode returns a unicode u'' string but only # plain '' strings can be used as keywords so we need to # stringify the names. properties[str(f.name)] = c.decode(f.type) return Header(klass, weight, size, **properties) def __str__(self): return "%s %s %s %s" % (self.klass, self.weight, self.size, self.properties) class Body(Payload): type = Frame.BODY def __init__(self, content): self.content = content def encode(self, enc): enc.encode_longstr(self.content) def decode(spec, dec): return Body(dec.decode_longstr()) def __str__(self): return "Body(%r)" % self.content class Heartbeat(Payload): type = Frame.HEARTBEAT def __str__(self): return "Heartbeat()" def encode(self, enc): enc.encode_long(0) def decode(spec, dec): dec.decode_long() return Heartbeat() txAMQP-0.6.1/src/txamqp/testlib.py0000644000175000017500000001742611533227505016336 0ustar esteveesteve# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # import os import warnings from txamqp.content import Content import txamqp.spec from txamqp.protocol import AMQClient from txamqp.client import TwistedDelegate from twisted.internet import error, protocol, reactor from twisted.trial import unittest from twisted.internet.defer import inlineCallbacks, Deferred, returnValue from txamqp.queue import Empty RABBITMQ = "RABBITMQ" OPENAMQ = "OPENAMQ" QPID = "QPID" class supportedBrokers(object): def __init__(self, *supporterBrokers): self.supporterBrokers = supporterBrokers def __call__(self, f): if _get_broker() not in self.supporterBrokers: f.skip = "Not supported for this broker." return f def _get_broker(): return os.environ.get("TXAMQP_BROKER") USERNAME='guest' PASSWORD='guest' VHOST='/' HEARTBEAT = 0 class TestBase(unittest.TestCase): clientClass = AMQClient heartbeat = HEARTBEAT def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) self.host = 'localhost' self.port = 5672 self.broker = _get_broker() if self.broker is None: warnings.warn( "Using default broker rabbitmq. Define TXAMQP_BROKER " "environment variable to customized it.") self.broker = RABBITMQ if self.broker == RABBITMQ: self.spec = '../specs/rabbitmq/amqp0-8.stripped.rabbitmq.xml' elif self.broker == OPENAMQ: self.spec = '../specs/standard/amqp0-9.stripped.xml' elif self.broker == QPID: self.spec = '../specs/qpid/amqp.0-8.xml' else: raise RuntimeError( "Unsupported broker '%s'. Use one of RABBITMQ, OPENAMQ or " "QPID" % self.broker) self.user = USERNAME self.password = PASSWORD self.vhost = VHOST self.queues = [] self.exchanges = [] self.connectors = [] @inlineCallbacks def connect(self, host=None, port=None, spec=None, user=None, password=None, vhost=None, heartbeat=None, clientClass=None): host = host or self.host port = port or self.port spec = spec or self.spec user = user or self.user password = password or self.password vhost = vhost or self.vhost heartbeat = heartbeat or self.heartbeat clientClass = clientClass or self.clientClass delegate = TwistedDelegate() onConn = Deferred() p = clientClass(delegate, vhost, txamqp.spec.load(spec), heartbeat=heartbeat) f = protocol._InstanceFactory(reactor, p, onConn) c = reactor.connectTCP(host, port, f) def errb(thefailure): thefailure.trap(error.ConnectionRefusedError) print "failed to connect to host: %s, port: %s; These tests are designed to run against a running instance" \ " of the %s AMQP broker on the given host and port. failure: %r" % (host, port, self.broker, thefailure,) thefailure.raiseException() onConn.addErrback(errb) self.connectors.append(c) client = yield onConn yield client.authenticate(user, password) returnValue(client) @inlineCallbacks def setUp(self): try: self.client = yield self.connect() except txamqp.client.Closed, le: le.args = tuple(("Unable to connect to AMQP broker in order to run tests (perhaps due to auth failure?). " \ "The tests assume that an instance of the %s AMQP broker is already set up and that this test script " \ "can connect to it and use it as user '%s', password '%s', vhost '%s'." % (_get_broker(), USERNAME, PASSWORD, VHOST),) + le.args) raise self.channel = yield self.client.channel(1) yield self.channel.channel_open() @inlineCallbacks def tearDown(self): for ch, q in self.queues: yield ch.queue_delete(queue=q) for ch, ex in self.exchanges: yield ch.exchange_delete(exchange=ex) for connector in self.connectors: yield connector.disconnect() @inlineCallbacks def queue_declare(self, channel=None, *args, **keys): channel = channel or self.channel reply = yield channel.queue_declare(*args, **keys) self.queues.append((channel, reply.queue)) returnValue(reply) @inlineCallbacks def exchange_declare(self, channel=None, ticket=0, exchange='', type='', passive=False, durable=False, auto_delete=False, internal=False, nowait=False, arguments={}): channel = channel or self.channel reply = yield channel.exchange_declare(ticket, exchange, type, passive, durable, auto_delete, internal, nowait, arguments) self.exchanges.append((channel,exchange)) returnValue(reply) def assertChannelException(self, expectedCode, message): self.assertEqual("channel", message.method.klass.name) self.assertEqual("close", message.method.name) self.assertEqual(expectedCode, message.reply_code) def assertConnectionException(self, expectedCode, message): self.assertEqual("connection", message.method.klass.name) self.assertEqual("close", message.method.name) self.assertEqual(expectedCode, message.reply_code) @inlineCallbacks def consume(self, queueName): """Consume from named queue returns the Queue object.""" reply = yield self.channel.basic_consume(queue=queueName, no_ack=True) returnValue((yield self.client.queue(reply.consumer_tag))) @inlineCallbacks def assertEmpty(self, queue): """Assert that the queue is empty""" try: yield queue.get(timeout=1) self.fail("Queue is not empty.") except Empty: None # Ignore @inlineCallbacks def assertPublishGet(self, queue, exchange="", routing_key="", properties=None): """ Publish to exchange and assert queue.get() returns the same message. """ body = self.uniqueString() self.channel.basic_publish(exchange=exchange, content=Content(body, properties=properties), routing_key=routing_key) msg = yield queue.get(timeout=1) self.assertEqual(body, msg.content.body) if (properties): self.assertEqual(properties, msg.content.properties) def uniqueString(self): """Generate a unique string, unique for this TestBase instance""" if not "uniqueCounter" in dir(self): self.uniqueCounter = 1; return "Test Message " + str(self.uniqueCounter) @inlineCallbacks def assertPublishConsume(self, queue="", exchange="", routing_key="", properties=None): """ Publish a message and consume it, assert it comes back intact. Return the Queue object used to consume. """ yield self.assertPublishGet((yield self.consume(queue)), exchange, routing_key, properties) txAMQP-0.6.1/src/txamqp/message.py0000644000175000017500000000451211525011461016275 0ustar esteveesteve# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # try: set except NameError: from sets import Set as set class Message(object): COMMON_FIELDS = set(("content", "method", "fields")) def __init__(self, method, fields, content = None): self.method = method self.fields = fields self.content = content def __len__(self): l = len(self.fields) if self.method.content: l += 1 return len(self.fields) def _idx(self, idx): if idx < 0: idx += len(self) if idx < 0 or idx > len(self): raise IndexError(idx) return idx def __getitem__(self, idx): idx = self._idx(idx) if idx == len(self.fields): return self.content else: return self.fields[idx] def __setitem__(self, idx, value): idx = self._idx(idx) if idx == len(self.fields): self.content = value else: self.fields[idx] = value def _slot(self, attr): if attr in Message.COMMON_FIELDS: env = self.__dict__ key = attr else: env = self.fields try: field = self.method.fields.bypyname[attr] key = self.method.fields.index(field) except KeyError: raise AttributeError(attr) return env, key def __getattr__(self, attr): env, key = self._slot(attr) return env[key] def __setattr__(self, attr, value): env, key = self._slot(attr) env[attr] = value STR = "%s %s content = %s" REPR = STR.replace("%s", "%r") def __str__(self): return Message.STR % (self.method, self.fields, self.content) def __repr__(self): return Message.REPR % (self.method, self.fields, self.content) txAMQP-0.6.1/src/txamqp/queue.py0000644000175000017500000000225511525011461015777 0ustar esteveesteve# coding: utf-8 from twisted.internet.defer import DeferredQueue class Empty(Exception): pass class Closed(Exception): pass class TimeoutDeferredQueue(DeferredQueue): END = object() def __init__(self, clock=None): if clock is None: from twisted.internet import reactor as clock self.clock = clock DeferredQueue.__init__(self) def _timeout(self, deferred): if not deferred.called: if deferred in self.waiting: self.waiting.remove(deferred) deferred.errback(Empty()) def _raiseIfClosed(self, result, call_id): if call_id is not None: call_id.cancel() if result == TimeoutDeferredQueue.END: self.put(TimeoutDeferredQueue.END) raise Closed() else: return result def get(self, timeout=None): deferred = DeferredQueue.get(self) call_id = None if timeout: call_id = self.clock.callLater(timeout, self._timeout, deferred) deferred.addCallback(self._raiseIfClosed, call_id) return deferred def close(self): self.put(TimeoutDeferredQueue.END) txAMQP-0.6.1/src/txamqp/spec.py0000644000175000017500000002424411525011461015607 0ustar esteveesteve# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # """ This module loads protocol metadata into python objects. It provides access to spec metadata via a python object model, and can also dynamically creating python methods, classes, and modules based on the spec metadata. All the generated methods have proper signatures and doc strings based on the spec metadata so the python help system can be used to browse the spec documentation. The generated methods all dispatch to the self.invoke(meth, args) callback of the containing class so that the generated code can be reused in a variety of situations. """ import re, textwrap, new from txamqp import xmlutil class SpecContainer(object): def __init__(self): self.items = [] self.byname = {} self.byid = {} self.indexes = {} self.bypyname = {} def add(self, item): if self.byname.has_key(item.name): raise ValueError("duplicate name: %s" % item) if self.byid.has_key(item.id): raise ValueError("duplicate id: %s" % item) pyname = pythonize(item.name) if self.bypyname.has_key(pyname): raise ValueError("duplicate pyname: %s" % item) self.indexes[item] = len(self.items) self.items.append(item) self.byname[item.name] = item self.byid[item.id] = item self.bypyname[pyname] = item def index(self, item): try: return self.indexes[item] except KeyError: raise ValueError(item) def __iter__(self): return iter(self.items) def __len__(self): return len(self.items) class Metadata(object): PRINT = [] def __init__(self): pass def __str__(self): args = map(lambda f: "%s=%s" % (f, getattr(self, f)), self.PRINT) return "%s(%s)" % (self.__class__.__name__, ", ".join(args)) def __repr__(self): return str(self) class Spec(Metadata): PRINT=["major", "minor", "file"] def __init__(self, major, minor, file): Metadata.__init__(self) self.major = major self.minor = minor self.file = file self.constants = SpecContainer() self.classes = SpecContainer() def post_load(self): self.module = self.define_module("amqp%s%s" % (self.major, self.minor)) self.klass = self.define_class("Amqp%s%s" % (self.major, self.minor)) def parse_method(self, name): parts = re.split(r"\s*\.\s*", name) if len(parts) != 2: raise ValueError(name) klass, meth = parts return self.classes.byname[klass].methods.byname[meth] def define_module(self, name, doc = None): module = new.module(name, doc) module.__file__ = self.file for c in self.classes: classname = pythonize(c.name) cls = c.define_class(classname) cls.__module__ = module.__name__ setattr(module, classname, cls) return module def define_class(self, name): methods = {} for c in self.classes: for m in c.methods: meth = pythonize(m.klass.name + "_" + m.name) methods[meth] = m.define_method(meth) return type(name, (), methods) class Constant(Metadata): PRINT=["name", "id"] def __init__(self, spec, name, id, klass, docs): Metadata.__init__(self) self.spec = spec self.name = name self.id = id self.klass = klass self.docs = docs class Class(Metadata): PRINT=["name", "id"] def __init__(self, spec, name, id, handler, docs): Metadata.__init__(self) self.spec = spec self.name = name self.id = id self.handler = handler self.fields = SpecContainer() self.methods = SpecContainer() self.docs = docs def define_class(self, name): methods = {} for m in self.methods: meth = pythonize(m.name) methods[meth] = m.define_method(meth) return type(name, (), methods) class Method(Metadata): PRINT=["name", "id"] def __init__(self, klass, name, id, content, responses, synchronous, description, docs): Metadata.__init__(self) self.klass = klass self.name = name self.id = id self.content = content self.responses = responses self.synchronous = synchronous self.fields = SpecContainer() self.description = description self.docs = docs self.response = False def docstring(self): s = "\n\n".join([fill(d, 2) for d in [self.description] + self.docs]) for f in self.fields: if f.docs: s += "\n\n" + "\n\n".join([fill(f.docs[0], 4, pythonize(f.name))] + [fill(d, 4) for d in f.docs[1:]]) return s METHOD = "__method__" DEFAULTS = {"bit": False, "shortstr": "", "longstr": "", "table": {}, "octet": 0, "short": 0, "long": 0, "longlong": 0, "timestamp": 0, "content": None} def define_method(self, name): g = {Method.METHOD: self} l = {} args = [(pythonize(f.name), Method.DEFAULTS[f.type]) for f in self.fields] if self.content: args += [("content", None)] code = "def %s(self, %s):\n" % \ (name, ", ".join(["%s = %r" % a for a in args])) code += " %r\n" % self.docstring() if self.content: methargs = args[:-1] else: methargs = args argnames = ", ".join([a[0] for a in methargs]) code += " return self.invoke(%s" % Method.METHOD if argnames: code += ", (%s,)" % argnames else: code += ", ()" if self.content: code += ", content" code += ")" exec code in g, l return l[name] class Field(Metadata): PRINT=["name", "id", "type"] def __init__(self, name, id, type, docs): Metadata.__init__(self) self.name = name self.id = id self.type = type self.docs = docs def get_docs(nd): return [n.text for n in nd["doc"]] def load_fields(nd, l, domains): for f_nd in nd["field"]: try: type = f_nd["@domain"] except KeyError: type = f_nd["@type"] while domains.has_key(type) and domains[type] != type: type = domains[type] l.add(Field(f_nd["@name"], f_nd.index(), type, get_docs(f_nd))) def load(specfile): doc = xmlutil.parse(specfile) return loadFromDoc(doc, specfilename=specfile) def loadString(specfilestr, specfilename=None): doc = xmlutil.parseString(specfilestr) return loadFromDoc(doc, specfilename=specfilename) def loadFromDoc(doc, specfilename=None): root = doc["amqp"][0] spec = Spec(int(root["@major"]), int(root["@minor"]), specfilename) # constants for nd in root["constant"]: const = Constant(spec, nd["@name"], int(nd["@value"]), nd.get("@class"), get_docs(nd)) spec.constants.add(const) # domains are typedefs domains = {} for nd in root["domain"]: domains[nd["@name"]] = nd["@type"] # classes for c_nd in root["class"]: klass = Class(spec, c_nd["@name"], int(c_nd["@index"]), c_nd["@handler"], get_docs(c_nd)) load_fields(c_nd, klass.fields, domains) for m_nd in c_nd["method"]: meth = Method(klass, m_nd["@name"], int(m_nd["@index"]), m_nd.get_bool("@content", False), [nd["@name"] for nd in m_nd["response"]], m_nd.get_bool("@synchronous", False), m_nd.text, get_docs(m_nd)) load_fields(m_nd, meth.fields, domains) klass.methods.add(meth) # resolve the responses for m in klass.methods: m.responses = [klass.methods.byname[r] for r in m.responses] for resp in m.responses: resp.response = True spec.classes.add(klass) spec.post_load() return spec REPLACE = {" ": "_", "-": "_"} KEYWORDS = {"global": "global_", "return": "return_"} def pythonize(name): name = str(name) for key, val in REPLACE.items(): name = name.replace(key, val) try: name = KEYWORDS[name] except KeyError: pass return name def fill(text, indent, heading = None): sub = indent * " " if heading: init = (indent - 2) * " " + heading + " -- " else: init = sub w = textwrap.TextWrapper(initial_indent = init, subsequent_indent = sub) return w.fill(" ".join(text.split())) class Rule(Metadata): PRINT = ["text", "implement", "tests"] def __init__(self, text, implement, tests, path): self.text = text self.implement = implement self.tests = tests self.path = path def find_rules(node, rules): if node.name == "rule": rules.append(Rule(node.text, node.get("@implement"), [ch.text for ch in node if ch.name == "test"], node.path())) if node.name == "doc" and node.get("@name") == "rule": tests = [] if node.has("@test"): tests.append(node["@test"]) rules.append(Rule(node.text, None, tests, node.path())) for child in node: find_rules(child, rules) def load_rules(specfile): rules = [] find_rules(xmlutil.parse(specfile), rules) return rules def test_summary(): template = """ AMQP Tests %s
""" rows = [] for rule in load_rules("amqp.org/specs/amqp7.xml"): if rule.tests: tests = ", ".join(rule.tests) else: tests = " " rows.append('Path: %s' 'Implement: %s' 'Tests: %s' % (rule.path[len("/root/amqp"):], rule.implement, tests)) rows.append('%s' % rule.text) rows.append(' ') print template % "\n".join(rows) txAMQP-0.6.1/src/txamqp/content.py0000644000175000017500000000307711525011461016330 0ustar esteveesteve# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # """ A simple python representation for AMQP content. """ def default(val, defval): if val == None: return defval else: return val class Content(object): def __init__(self, body = "", children = None, properties = None): self.body = body self.children = default(children, []) self.properties = default(properties, {}) def size(self): return len(self.body) def weight(self): return len(self.children) def __getitem__(self, name): return self.properties[name] def __setitem__(self, name, value): self.properties[name] = value def __delitem__(self, name): del self.properties[name] def __repr__(self): return '<%s instance: body=%r, children=%r, properties=%r>' % ( self.__class__.__name__, self.body, self.children, self.properties) txAMQP-0.6.1/src/txamqp/protocol.py0000644000175000017500000003042711533230251016515 0ustar esteveesteve# coding: utf-8 from twisted.internet import defer, protocol from twisted.internet.task import LoopingCall from twisted.protocols import basic from txamqp import spec from txamqp.codec import Codec from txamqp.connection import Header, Frame, Method, Body, Heartbeat from txamqp.message import Message from txamqp.content import Content from txamqp.queue import TimeoutDeferredQueue, Closed as QueueClosed from txamqp.client import TwistedEvent, Closed from cStringIO import StringIO import struct from time import time class GarbageException(Exception): pass # An AMQP channel is a virtual connection that shares the # same socket with others channels. One can have many channels # per connection class AMQChannel(object): def __init__(self, id, outgoing): self.id = id self.outgoing = outgoing self.incoming = TimeoutDeferredQueue() self.responses = TimeoutDeferredQueue() self.queue = None self.closed = False self.reason = None def close(self, reason): if self.closed: return self.closed = True self.reason = reason self.incoming.close() self.responses.close() def dispatch(self, frame, work): payload = frame.payload if isinstance(payload, Method): if payload.method.response: self.queue = self.responses else: self.queue = self.incoming work.put(self.incoming) self.queue.put(frame) @defer.inlineCallbacks def invoke(self, method, args, content=None): if self.closed: raise Closed(self.reason) frame = Frame(self.id, Method(method, *args)) self.outgoing.put(frame) if method.content: if content == None: content = Content() self.writeContent(method.klass, content, self.outgoing) try: # here we depend on all nowait fields being named nowait f = method.fields.byname["nowait"] nowait = args[method.fields.index(f)] except KeyError: nowait = False try: if not nowait and method.responses: resp = (yield self.responses.get()).payload if resp.method.content: content = yield readContent(self.responses) else: content = None if resp.method in method.responses: defer.returnValue(Message(resp.method, resp.args, content)) else: raise ValueError(resp) except QueueClosed, e: if self.closed: raise Closed(self.reason) else: raise e def writeContent(self, klass, content, queue): size = content.size() header = Frame(self.id, Header(klass, content.weight(), size, **content.properties)) queue.put(header) for child in content.children: self.writeContent(klass, child, queue) # should split up if content.body exceeds max frame size if size > 0: queue.put(Frame(self.id, Body(content.body))) class FrameReceiver(protocol.Protocol, basic._PauseableMixin): frame_mode = False MAX_LENGTH = 4096 HEADER_LENGTH = 1 + 2 + 4 + 1 __buffer = '' def __init__(self, spec): self.spec = spec self.FRAME_END = self.spec.constants.bypyname["frame_end"].id # packs a frame and writes it to the underlying transport def sendFrame(self, frame): data = self._packFrame(frame) self.transport.write(data) # packs a frame, see qpid.connection.Connection#write def _packFrame(self, frame): s = StringIO() c = Codec(s) c.encode_octet(self.spec.constants.bypyname[frame.payload.type].id) c.encode_short(frame.channel) frame.payload.encode(c) c.encode_octet(self.FRAME_END) data = s.getvalue() return data # unpacks a frame, see qpid.connection.Connection#read def _unpackFrame(self, data): s = StringIO(data) c = Codec(s) frameType = spec.pythonize(self.spec.constants.byid[c.decode_octet()].name) channel = c.decode_short() payload = Frame.DECODERS[frameType].decode(self.spec, c) end = c.decode_octet() if end != self.FRAME_END: raise GarbageException('frame error: expected %r, got %r' % (self.FRAME_END, end)) frame = Frame(channel, payload) return frame def setRawMode(self): self.frame_mode = False def setFrameMode(self, extra=''): self.frame_mode = True if extra: return self.dataReceived(extra) def dataReceived(self, data): self.__buffer = self.__buffer + data while self.frame_mode and not self.paused: sz = len(self.__buffer) - self.HEADER_LENGTH if sz >= 0: length, = struct.unpack("!I", self.__buffer[3:7]) # size = 4 bytes if sz >= length: packet = self.__buffer[:self.HEADER_LENGTH + length] self.__buffer = self.__buffer[self.HEADER_LENGTH + length:] frame = self._unpackFrame(packet) why = self.frameReceived(frame) if why or self.transport and self.transport.disconnecting: return why else: continue if len(self.__buffer) > self.MAX_LENGTH: frame, self.__buffer = self.__buffer, '' return self.frameLengthExceeded(frame) break else: if not self.paused: data = self.__buffer self.__buffer = '' if data: return self.rawDataReceived(data) def sendInitString(self): initString = "!4s4B" s = StringIO() c = Codec(s) c.pack(initString, "AMQP", 1, 1, self.spec.major, self.spec.minor) self.transport.write(s.getvalue()) @defer.inlineCallbacks def readContent(queue): frame = yield queue.get() header = frame.payload children = [] for i in range(header.weight): content = yield readContent(queue) children.append(content) size = header.size read = 0 buf = StringIO() while read < size: body = yield queue.get() content = body.payload.content buf.write(content) read += len(content) defer.returnValue(Content(buf.getvalue(), children, header.properties.copy())) class AMQClient(FrameReceiver): channelClass = AMQChannel # Max unreceived heartbeat frames. The AMQP standard says it's 3. MAX_UNSEEN_HEARTBEAT = 3 def __init__(self, delegate, vhost, spec, heartbeat=0, clock=None, insist=False): FrameReceiver.__init__(self, spec) self.delegate = delegate # XXX Cyclic dependency self.delegate.client = self self.vhost = vhost self.channelFactory = type("Channel%s" % self.spec.klass.__name__, (self.channelClass, self.spec.klass), {}) self.channels = {} self.channelLock = defer.DeferredLock() self.outgoing = defer.DeferredQueue() self.work = defer.DeferredQueue() self.started = TwistedEvent() self.queueLock = defer.DeferredLock() self.basic_return_queue = TimeoutDeferredQueue() self.queues = {} self.outgoing.get().addCallback(self.writer) self.work.get().addCallback(self.worker) self.heartbeatInterval = heartbeat self.insist = insist if self.heartbeatInterval > 0: if clock is None: from twisted.internet import reactor as clock self.clock = clock self.checkHB = self.clock.callLater(self.heartbeatInterval * self.MAX_UNSEEN_HEARTBEAT, self.checkHeartbeat) self.sendHB = LoopingCall(self.sendHeartbeat) d = self.started.wait() d.addCallback(lambda _: self.reschedule_sendHB()) d.addCallback(lambda _: self.reschedule_checkHB()) def reschedule_sendHB(self): if self.heartbeatInterval > 0: if self.sendHB.running: self.sendHB.stop() self.sendHB.start(self.heartbeatInterval, now=False) def reschedule_checkHB(self): if self.checkHB.active(): self.checkHB.cancel() self.checkHB = self.clock.callLater(self.heartbeatInterval * self.MAX_UNSEEN_HEARTBEAT, self.checkHeartbeat) def check_0_8(self): return (self.spec.minor, self.spec.major) == (0, 8) @defer.inlineCallbacks def channel(self, id): yield self.channelLock.acquire() try: try: ch = self.channels[id] except KeyError: ch = self.channelFactory(id, self.outgoing) self.channels[id] = ch finally: self.channelLock.release() defer.returnValue(ch) @defer.inlineCallbacks def queue(self, key): yield self.queueLock.acquire() try: try: q = self.queues[key] except KeyError: q = TimeoutDeferredQueue() self.queues[key] = q finally: self.queueLock.release() defer.returnValue(q) def close(self, reason): for ch in self.channels.values(): ch.close(reason) for q in self.queues.values(): q.close() self.delegate.close(reason) def writer(self, frame): self.sendFrame(frame) self.outgoing.get().addCallback(self.writer) def worker(self, queue): d = self.dispatch(queue) def cb(ign): self.work.get().addCallback(self.worker) d.addCallback(cb) d.addErrback(self.close) @defer.inlineCallbacks def dispatch(self, queue): frame = yield queue.get() channel = yield self.channel(frame.channel) payload = frame.payload if payload.method.content: content = yield readContent(queue) else: content = None # Let the caller deal with exceptions thrown here. message = Message(payload.method, payload.args, content) self.delegate.dispatch(channel, message) # As soon as we connect to the target AMQP broker, send the init string def connectionMade(self): self.sendInitString() self.setFrameMode() def frameReceived(self, frame): self.processFrame(frame) def sendFrame(self, frame): if frame.payload.type != Frame.HEARTBEAT: self.reschedule_sendHB() FrameReceiver.sendFrame(self, frame) @defer.inlineCallbacks def processFrame(self, frame): ch = yield self.channel(frame.channel) if frame.payload.type == Frame.HEARTBEAT: self.lastHBReceived = time() else: ch.dispatch(frame, self.work) if self.heartbeatInterval > 0: self.reschedule_checkHB() @defer.inlineCallbacks def authenticate(self, username, password, mechanism='AMQPLAIN', locale='en_US'): if self.check_0_8(): response = {"LOGIN": username, "PASSWORD": password} else: response = "\0" + username + "\0" + password yield self.start(response, mechanism, locale) @defer.inlineCallbacks def start(self, response, mechanism='AMQPLAIN', locale='en_US'): self.response = response self.mechanism = mechanism self.locale = locale yield self.started.wait() channel0 = yield self.channel(0) if self.check_0_8(): result = yield channel0.connection_open(self.vhost, insist=self.insist) else: result = yield channel0.connection_open(self.vhost) defer.returnValue(result) def sendHeartbeat(self): self.sendFrame(Frame(0, Heartbeat())) self.lastHBSent = time() def checkHeartbeat(self): if self.checkHB.active(): self.checkHB.cancel() self.transport.loseConnection() def connectionLost(self, reason): if self.heartbeatInterval > 0: if self.sendHB.running: self.sendHB.stop() if self.checkHB.active(): self.checkHB.cancel() self.close(reason) txAMQP-0.6.1/src/txamqp/codec.py0000644000175000017500000001272011725125723015737 0ustar esteveesteve#!/usr/bin/env python # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # """ Utility code to translate between python objects and AMQP encoded data fields. """ from cStringIO import StringIO from struct import pack, calcsize, unpack class EOF(Exception): pass class Codec(object): def __init__(self, stream): self.stream = stream self.nwrote = 0 self.nread = 0 self.incoming_bits = [] self.outgoing_bits = [] def read(self, n): data = self.stream.read(n) if n > 0 and len(data) == 0: raise EOF() self.nread += len(data) return data def write(self, s): self.flushbits() self.stream.write(s) self.nwrote += len(s) def flush(self): self.flushbits() self.stream.flush() def flushbits(self): if len(self.outgoing_bits) > 0: bytes = [] index = 0 for b in self.outgoing_bits: if index == 0: bytes.append(0) if b: bytes[-1] |= 1 << index index = (index + 1) % 8 del self.outgoing_bits[:] for byte in bytes: self.encode_octet(byte) def pack(self, fmt, *args): self.write(pack(fmt, *args)) def unpack(self, fmt): size = calcsize(fmt) data = self.read(size) values = unpack(fmt, data) if len(values) == 1: return values[0] else: return values def encode(self, type, value): getattr(self, "encode_" + type)(value) def decode(self, type): return getattr(self, "decode_" + type)() # bit def encode_bit(self, o): if o: self.outgoing_bits.append(True) else: self.outgoing_bits.append(False) def decode_bit(self): if len(self.incoming_bits) == 0: bits = self.decode_octet() for i in range(8): self.incoming_bits.append(bits >> i & 1 != 0) return self.incoming_bits.pop(0) # octet def encode_octet(self, o): self.pack("!B", o) def decode_octet(self): return self.unpack("!B") # short def encode_short(self, o): self.pack("!H", o) def decode_short(self): return self.unpack("!H") # long def encode_long(self, o): self.pack("!L", o) def decode_long(self): return self.unpack("!L") # longlong def encode_longlong(self, o): self.pack("!Q", o) def decode_longlong(self): return self.unpack("!Q") def enc_str(self, fmt, s): size = len(s) self.pack(fmt, size) self.write(s) def dec_str(self, fmt): size = self.unpack(fmt) return self.read(size) # shortstr def encode_shortstr(self, s): self.enc_str("!B", s) def decode_shortstr(self): return self.dec_str("!B") # longstr def encode_longstr(self, s): if isinstance(s, dict): self.encode_table(s) else: self.enc_str("!L", s) def decode_longstr(self): return self.dec_str("!L") # timestamp def encode_timestamp(self, o): self.pack("!Q", o) def decode_timestamp(self): return self.unpack("!Q") # table def encode_table(self, tbl): enc = StringIO() codec = Codec(enc) for key, value in tbl.items(): codec.encode_shortstr(key) if isinstance(value, basestring): codec.write("S") codec.encode_longstr(value) else: codec.write("I") codec.encode_long(value) s = enc.getvalue() self.encode_long(len(s)) self.write(s) def decode_table(self): size = self.decode_long() start = self.nread result = {} while self.nread - start < size: key = self.decode_shortstr() type = self.read(1) if type == "S": value = self.decode_longstr() elif type == "I": value = self.decode_long() elif type == "F": value = self.decode_table() elif type == "t": value = (self.decode_octet() != 0) else: raise ValueError(repr(type)) result[key] = value return result def test(type, value): if isinstance(value, (list, tuple)): values = value else: values = [value] stream = StringIO() codec = Codec(stream) for v in values: codec.encode(type, v) codec.flush() enc = stream.getvalue() stream.reset() dup = [] for i in xrange(len(values)): dup.append(codec.decode(type)) if values != dup: raise AssertionError("%r --> %r --> %r" % (values, enc, dup)) if __name__ == "__main__": def dotest(type, value): args = (type, value) test(*args) for value in ("1", "0", "110", "011", "11001", "10101", "10011"): for i in range(10): dotest("bit", map(lambda x: x == "1", value*i)) for value in ({}, {"asdf": "fdsa", "fdsa": 1, "three": 3}, {"one": 1}): dotest("table", value) for type in ("octet", "short", "long", "longlong"): for value in range(0, 256): dotest(type, value) for type in ("shortstr", "longstr"): for value in ("", "a", "asdf"): dotest(type, value) txAMQP-0.6.1/src/txamqp/xmlutil.py0000644000175000017500000000566511525011461016361 0ustar esteveesteve# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # """ XML utilities used by spec.py """ import xml.sax from xml.sax.handler import ContentHandler def parse(file): doc = Node("root") xml.sax.parse(file, Builder(doc)) return doc def parseString(string): doc = Node("root") xml.sax.parseString(string, Builder(doc)) return doc class Node(object): def __init__(self, name, attrs = None, text = None, parent = None): self.name = name self.attrs = attrs self.text = text self.parent = parent self.children = [] if parent != None: parent.children.append(self) def get_bool(self, key, default = False): v = self.get(key) if v == None: return default else: return bool(int(v)) def index(self): if self.parent: return self.parent.children.index(self) else: return 0 def has(self, key): try: self[key] return True except KeyError: return False except IndexError: return False def get(self, key, default = None): if self.has(key): return self[key] else: return default def __getitem__(self, key): if callable(key): return filter(key, self.children) else: t = key.__class__ meth = "__get%s__" % t.__name__ if hasattr(self, meth): return getattr(self, meth)(key) else: raise KeyError(key) def __getstr__(self, name): if name[:1] == "@": return self.attrs[name[1:]] else: return self[lambda nd: nd.name == name] def __getint__(self, index): return self.children[index] def __iter__(self): return iter(self.children) def path(self): if self.parent == None: return "/%s" % self.name else: return "%s/%s" % (self.parent.path(), self.name) class Builder(ContentHandler): def __init__(self, start = None): self.node = start def __setitem__(self, element, type): self.types[element] = type def startElement(self, name, attrs): self.node = Node(name, attrs, None, self.node) def endElement(self, name): self.node = self.node.parent def characters(self, content): if self.node.text == None: self.node.text = content else: self.node.text += content txAMQP-0.6.1/setup.py0000644000175000017500000000225211726126046013720 0ustar esteveestevesetupdict= { 'name': 'txAMQP', 'version': '0.6.1', 'author': 'Esteve Fernandez', 'author_email': 'esteve@fluidinfo.com', 'url': 'https://launchpad.net/txamqp', 'description': 'Python library for communicating with AMQP peers and brokers using Twisted', 'long_description': '''This project contains all the necessary code to connect, send and receive messages to/from an AMQP-compliant peer or broker (Qpid, OpenAMQ, RabbitMQ) using Twisted. It also includes support for using Thrift RPC over AMQP in Twisted applications. txAMQP is sponsored by the friendly folks at Fluidinfo (http://www.fluidinfo.com).''' } try: from setuptools import setup, find_packages except ImportError: from distutils.core import setup setupdict['packages'] = ['txamqp', 'txamqp.contrib', 'txamqp.contrib.thrift'] setupdict['package_dir'] = { 'txamqp': 'src/txamqp', 'txamqp.contrib': 'src/txamqp/contrib', 'txamqp.contrib.thrift': 'src/txamqp/contrib/thrift', } else: setupdict['packages'] = find_packages('src') setupdict['package_dir'] = { '': 'src' } setupdict['install_requires'] = ['Twisted'] setup(**setupdict)