diff --git a/README.md b/README.md index 1c7e049f56f12167f83f1d9e160f1b28cc7fcc2e..ec4bf1b7ce1c4ed040c826bb93a244c3b90638e7 100644 --- a/README.md +++ b/README.md @@ -8,31 +8,13 @@ CoolAMQP []() []() -When you're tired of fucking with AMQP reconnects. +A **magical** AMQP client, that uses **heavy sorcery** to achieve speeds that other AMQP clients cannot even hope to match. -When a connection made by CoolAMQP to your broker fails, it will pick another -node, redeclare exchanges, queues, consumers, QoS and all the other shit, and tell -your application that a disconnect happened. +tl;dr - [this](coolamqp/framing/definitions.py) is **machine-generated** compile-time. +[this](coolamqp/framing/compilation/content_property.py) **generates classes run-time**. -You only need to remember that: - -1. Reconnects and redefinitions take a while. Things will happen during that time. It is your responsibility to ensure that your distributed system is built to handle this -2. CoolAMQP will tell you when it senses losing broker connection. It will also tell you when it regains the connection (that means that everything is redefined and ready to go) -3. Delivering messages multiple times may happen. Ensure you know when it happens. Keywords: message acknowledgement, amqp specification -4. CoolAMQP won't touch your messages. You send bags of bytes and properties, you get bags of bytes and their properties. This is by design - the postman shouldn't mess with your mail. The project is actively maintained and used in a commercial project. Tests can run either on Vagrant (Vagrantfile attached) or Travis CI, and run against RabbitMQ. Enjoy! - -# Changelog -## v0.12 -* ACCESS_REFUSED/RESOURCE_LOCKED on reconnect is properly handled -* reason for consumer cancel is provided -* can read error code and reply text from failed orders -* test suite refactored and improved -## v0.11 -* added *no_ack* to *consume* -* can pass other non-text types to Message -* can set global bit in *qos* diff --git a/coolamqp/framing/base.py b/coolamqp/framing/base.py index 6ddeddf3e8f5d79a3079492ed240351d0b42ff37..f9c33bb697e90b38cbd80b075314e853d1e90607 100644 --- a/coolamqp/framing/base.py +++ b/coolamqp/framing/base.py @@ -86,6 +86,43 @@ class AMQPContentPropertyList(object): """ PROPERTIES = [] + @staticmethod + def zero_property_flags(property_flags): + """ + Given a binary property_flags, set all bit properties to 0. + + This leaves us with a canonical representation, that can be used + in obtaining a particular property list + :param property_flags: binary + :return: binary + """ + # this is a default implementation. + # compiler should emit it's own when the content property list has a + # possible bit field + return property_flags + + def write_to(self, buf): + """Serialize itself (flags + values) to a buffer""" + raise NotImplementedError + + @staticmethod + def from_buffer(self, buf, start_offset): + """ + Return an instance of self, loaded from a buffer. + + This does not have to return length, because it is always passed exactly enough of a buffer. + + Buffer HAS TO start at property_flags + """ + raise NotImplementedError + + def get_size(self): + """ + How long is property_flags + property_values + :return: int + """ + raise NotImplementedError + class AMQPMethodPayload(AMQPPayload): RESPONSE_TO = None diff --git a/coolamqp/framing/compilation/compile_definitions.py b/coolamqp/framing/compilation/compile_definitions.py index 1d2f91ee5aa7b39fd95a0ce3ef6a69716070b335..3ded538b3b618d8d2ed2bb0e4e4689f5c4c5239f 100644 --- a/coolamqp/framing/compilation/compile_definitions.py +++ b/coolamqp/framing/compilation/compile_definitions.py @@ -42,13 +42,15 @@ AMQP is copyright (c) 2016 OASIS CoolAMQP is copyright (c) 2016 DMS Serwis s.c. """ -import struct -import collections +import struct, collections, warnings, logging, six -from coolamqp.framing.base_definitions import AMQPClass, AMQPMethodPayload, AMQPContentPropertyList +from coolamqp.framing.base import AMQPClass, AMQPMethodPayload, AMQPContentPropertyList from coolamqp.framing.field_table import enframe_table, deframe_table, frame_table_size +from coolamqp.framing.compilation.content_property import compile_particular_content_property_list_class + +logger = logging.getLogger(__name__) -Field = collections.namedtuple('Field', ('name', 'type', 'reserved')) +Field = collections.namedtuple('Field', ('name', 'type', 'basic_type', 'reserved')) ''') @@ -91,6 +93,10 @@ Field = collections.namedtuple('Field', ('name', 'type', 'reserved')) class_id_to_contentpropertylist = {} + # below are stored as strings! + methods_that_are_reply_reasons_for = {} # eg. ConnectionOpenOk: ConnectionOk + methods_that_are_replies_for = {} # eg. ConnectionOk: [ConnectionOpenOk] + # Output classes for cls in get_classes(xml): @@ -121,13 +127,100 @@ Field = collections.namedtuple('Field', ('name', 'type', 'reserved')) is_static = all(property.basic_type not in ('table', 'longstr', 'shortstr') for property in cls.properties) for property in cls.properties: - line(' Field(%s, %s, %s),\n', frepr(property.name), frepr(property.basic_type), repr(property.reserved)) - line(' ]\n\n') - - - + if property.basic_type == 'bit': + raise ValueError('bit properties are not supported!' + ) + line(' Field(%s, %s, %s, %s),\n', frepr(property.name), frepr(property.type), frepr(property.basic_type), repr(property.reserved)) + line(''' ] + # A dictionary from a zero property list to a class typized with + # some fields + PARTICULAR_CLASSES = {} +\n''', + name_class(cls.name)) + + if any(prop.basic_type == 'bit' for prop in cls.properties): + raise NotImplementedError('I should emit a custom zero_property_list staticmethod :(') + line(u''' def __new__(self, **kwargs): + """ + Return a property list. +''') + property_strs = [] + my_props = [prop for prop in cls.properties if (not prop.reserved)] + for property in my_props: + line(' :param %s: %s\n', format_field_name(property.name), property.label) + line(' :type %s: %s (AMQP as %s)\n', format_field_name(property.name), TYPE_TRANSLATOR[property.basic_type], property.basic_type) + line(' """\n') + zpf_len = int(math.ceil(len(cls.properties) // 15)) + + first_byte = True # in 2-byte group + piece_index = 7 # from 7 downto 0 + fields_remaining = len(cls.properties) + + byte_chunk = [] + line(u' zpf = bytearray([\n') + + for field in cls.properties: + # a bit + if piece_index > 0: + if field.reserved or field.basic_type == 'bit': + byte_chunk.append(u"kwargs['%s']" % (2**piece_index, )) + else: + byte_chunk.append(u"(('%s' in kwargs) << %s)" % (format_field_name(field.name), piece_index)) + piece_index -= 1 + else: + if first_byte: + if field.reserved or field.basic_type == 'bit': + byte_chunk.append(u'0') + else: + byte_chunk.append(u"int('%s' in kwargs)" % (format_field_name(field.name),)) + else: + # this is the "do we need moar flags" section + byte_chunk.append(u"kwargs['%s']" % ( + int(fields_remaining > 1) + )) + + # Emit the byte + line(u' %s,\n', u' | '.join(byte_chunk)) + byte_chunk = [] + first_byte = not first_byte + piece_index = 7 + fields_remaining -= 1 + + if len(byte_chunk) > 0: + line(u' %s\n', u' | '.join(byte_chunk)) # We did not finish + + line(u' ])\n zpf = six.binary_type(zpf)\n') + line(u''' + if zpf in %s.PARTICULAR_CLASSES: + warnings.warn(u"""You could go faster. + + If you know in advance what properties you will be using, use typized constructors like + + # runs once + my_type = BasicContentPropertyList.typize('content_type', 'content_encoding') + # runs many times + props = my_type('text/plain', 'utf8') + + instead of + + # runs many times + props = BasicContentPropertyList(content_type='text/plain', content_encoding='utf8') + + This way you will be faster. + + If you do not know in advance what properties you will be using, it is correct to use + this constructor. + """) + + return %s.PARTICULAR_CLASSES[zpf](**kwargs) + else: + logger.debug('Property field (%s:%d) not seen yet, compiling', repr(zpf)) + c = compile_particular_content_property_list_class(zpf, %s.FIELDS) + %s.PARTICULAR_CLASSES[zpf] = c + return c(**kwargs) +'''.replace('%s', name_class(cls.name) + 'ContentPropertyList').replace('%d', '%s')) # ============================================ Do methods for this class @@ -153,7 +246,6 @@ Field = collections.namedtuple('Field', ('name', 'type', 'reserved')) BINARY_HEADER = %s # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = %s, %s - REPLY_WITH = [%s] # methods you can reply with to this one IS_SIZE_STATIC = %s # this means that argument part has always the same length IS_CONTENT_STATIC = %s # this means that argument part has always the same content @@ -165,11 +257,16 @@ Field = collections.namedtuple('Field', ('name', 'type', 'reserved')) to_code_binary(chr(cls.index)+chr(method.index)), repr(method.sent_by_client), repr(method.sent_by_server), - u', '.join([name_class(cls.name) + format_method_class_name(kidname) for kidname in method.response]), repr(is_static), repr(is_content_static) ) + _namify = lambda x: name_class(cls.name) + format_method_class_name(x) + + methods_that_are_replies_for[full_class_name] = [] + for response in method.response: + methods_that_are_reply_reasons_for[_namify(response)] = full_class_name + methods_that_are_replies_for[full_class_name].append(_namify(response)) if is_content_static: @@ -179,20 +276,13 @@ Field = collections.namedtuple('Field', ('name', 'type', 'reserved')) method.get_static_body() + \ struct.pack('!B', FRAME_END))) - # Am I a response somewhere? - for paren in cls.methods: - if method.name in paren.response: - line(' RESPONSE_TO = %s%s # this is sent in response to %s\n', name_class(cls.name), format_method_class_name(paren.name), - cls.name +'.' + paren.name - ) - # fields if len(method.fields) > 0: line('\n # See constructor pydoc for details\n') line(' FIELDS = [ \n') for field in method.fields: - line(' Field(%s, %s, reserved=%s),\n', frepr(field.name), frepr(field.basic_type), repr(field.reserved)) + line(' Field(%s, %s, %s, reserved=%s),\n', frepr(field.name), frepr(field.type), frepr(field.basic_type), repr(field.reserved)) line(' ]\n') @@ -209,6 +299,7 @@ Field = collections.namedtuple('Field', ('name', 'type', 'reserved')) if len(non_reserved_fields) > 0: line('\n') + for field in non_reserved_fields: if (field.label is not None) or (field.docs is not None): line(' :param %s: %s\n', format_field_name(field.name), @@ -268,6 +359,23 @@ Field = collections.namedtuple('Field', ('name', 'type', 'reserved')) line(' %s: %s,\n', k, v) line('}\n\n') + line(u'''# Methods that are sent as replies to other methods, ie. ConnectionOpenOk: ConnectionOpen +# if a method is NOT a reply, it will not be in this dict +# a method may be a reply for AT MOST one method +REPLY_REASONS_FOR = {\n''') + for k,v in methods_that_are_reply_reasons_for.items(): + line(u' %s: %s,\n' % (k, v)) + + line(u'''} + +# Methods that are replies for other, ie. ConnectionOpenOk: ConnectionOpen +# a method may be a reply for ONE or NONE other methods +# if a method has no replies, it will have an empty list as value here +REPLIES_FOR= {\n''') + + for k,v in methods_that_are_replies_for.items(): + line(u' %s: [%s],\n' % (k, u', '.join(map(str, v)))) + line(u'}\n') out.close() diff --git a/coolamqp/framing/compilation/content_property.py b/coolamqp/framing/compilation/content_property.py index ab85174b937600be8bb6fe49926fe0f996e898c6..4bd50c0d2572c65d35d32543bc3f6dbb363cbd7e 100644 --- a/coolamqp/framing/compilation/content_property.py +++ b/coolamqp/framing/compilation/content_property.py @@ -1,27 +1,106 @@ # coding=UTF-8 from __future__ import absolute_import, division, print_function -""" -Generate serializers/unserializers/length getters for given property_flags -""" +"""Generate serializers/unserializers/length getters for given property_flags""" +import six +import struct +import logging +from coolamqp.framing.compilation.textcode_fields import get_counter, get_from_buffer, get_serializer +from coolamqp.framing.base import AMQPContentPropertyList +from coolamqp.framing.field_table import enframe_table, deframe_table, frame_table_size + -class ContentPropertyListCompiler(object): +logger = logging.getLogger(__name__) + + +def _compile_particular_content_property_list_class(zpf, fields): """ - This produces serializers, unserializers and size getters for any property_flags combinations. + Compile a particular content property list. - Sure you could do that by hand, but how much faster is it to have most common messages precompiled? + Particularity stems from + :param zpf: zero property list, as bytearray + :param fields: list of all possible fields in this content property """ + from coolamqp.framing.compilation.utilities import format_field_name - MAX_COMPILERS_TO_KEEP = 8 + if any(field.basic_type == 'bit' for field in fields): + return u"raise NotImplementedError('I don't support bits in properties yet')" - def __init__(self, content_property_list_class): + # Convert ZPF to a list of [if_exists::bool] + even = True + zpf_bits = [] + for q in bytearray(zpf): + p = bin(q)[2:] + p = (u'0' * (8 - len(p))) + p + if not even: + p = p[:7] -def compile_for(fields): - """Compile a serializer, unserializer and length calculator for a list of fields""" + zpf_bits.extend(map(lambda x: bool(int(x)), p)) + + zpf_length = len(zpf) + + # 1 here does not mean that field is present. All bit fields are present, but 0 in a ZPF. Fix this. + zpf_bits = [zpf_bit or field.type == 'bit' for zpf_bit, field in zip(zpf_bits, fields)] + + mod = [u'''class ParticularContentTypeList(AMQPContentPropertyList): + """ + For fields: +'''] + + for field in fields: + mod.append(u' * %s::%s' % (format_field_name(field.name), field.type)) + if field.reserved: + mod.append(u' (reserved)') + mod.append(u'\n') + + x = repr(six.binary_type(zpf)) + if not x.startswith('b'): + x = 'b'+x + + present_fields = [field for field, present in zip(fields, zpf_bits) if present] + + mod.append(u''' + """ + # A value for property flags that is used, assuming all bit fields are FALSE (0) + ZERO_PROPERTY_FLAGS = %s + + def __init__(self, %s): +''' % (x, u', '.join(format_field_name(field.name) for field in present_fields))) + + for field in present_fields: + mod.append(u' self.%s = %s\n'.replace(u'%s', format_field_name(field.name))) + + # Let's do write_to + mod.append(u'\n def write_to(self, buf):\n') + mod.append(u' buf.write(') + repred_zpf = repr(zpf) + if not zpf.startswith('b'): + repred_zpf = 'b' + repred_zpf + mod.append(repred_zpf) + mod.append(u')\n') + + mod.append(get_serializer(present_fields, prefix='self.', indent_level=2)) + + # from_buffer + # note that non-bit values + mod.append(u' def from_buffer(self, buf, start_offset):\n offset = start_offset + %s\n' % (zpf_length, )) + mod.append(get_from_buffer( + present_fields + , prefix='', indent_level=2)) + mod.append(u' return ParticularContentTypeList(%s)\n' % + u', '.join(format_field_name(field.name) for field in present_fields)) + + + # get_size + mod.append(u'\n def get_size(self):\n') + mod.append(get_counter(present_fields, prefix='self.', indent_level=2)[:-1]) # skip eol + mod.append(u' + %s\n' % (zpf_length, )) # account for pf length + + return u''.join(mod) - mod = u'''# coding=UTF-8 -import struct -from coolamqp.framing.field_table import enframe_table, deframe_table, frame_table_size -def write_to( -''' \ No newline at end of file +def compile_particular_content_property_list_class(zpf, fields): + q = _compile_particular_content_property_list_class(zpf, fields) + logger.debug('Compiling\n%s', q) + exec(q) + return ParticularContentTypeList diff --git a/coolamqp/framing/compilation/utilities.py b/coolamqp/framing/compilation/utilities.py index f847007c2a2ccdf9f53b4be492210815fb6fda99..550f6605a2aa15804d799e57880084161a049db6 100644 --- a/coolamqp/framing/compilation/utilities.py +++ b/coolamqp/framing/compilation/utilities.py @@ -160,7 +160,6 @@ def format_method_class_name(methodname): @as_unicode def format_field_name(field): - print(repr(field)) if field in (u'global', u'type'): field = field + '_' return field.replace('-', '_') diff --git a/coolamqp/framing/definitions.py b/coolamqp/framing/definitions.py index c7a5d57839c0a89f24b81bc132d7952263ccac11..895b13396b77a0dfef6142f921a16f0697cc0827 100644 --- a/coolamqp/framing/definitions.py +++ b/coolamqp/framing/definitions.py @@ -10,13 +10,15 @@ AMQP is copyright (c) 2016 OASIS CoolAMQP is copyright (c) 2016 DMS Serwis s.c. """ -import struct -import collections +import struct, collections, warnings, logging, six -from coolamqp.framing.base_definitions import AMQPClass, AMQPMethodPayload, AMQPContentPropertyList +from coolamqp.framing.base import AMQPClass, AMQPMethodPayload, AMQPContentPropertyList from coolamqp.framing.field_table import enframe_table, deframe_table, frame_table_size +from coolamqp.framing.compilation.content_property import compile_particular_content_property_list_class -Field = collections.namedtuple('Field', ('name', 'type', 'reserved')) +logger = logging.getLogger(__name__) + +Field = collections.namedtuple('Field', ('name', 'type', 'basic_type', 'reserved')) # Core constants FRAME_METHOD = 1 @@ -122,17 +124,16 @@ class ConnectionClose(AMQPMethodPayload): BINARY_HEADER = b'\x0A\x32' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, True - REPLY_WITH = [ConnectionCloseOk] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'reply-code', u'short', reserved=False), - Field(u'reply-text', u'shortstr', reserved=False), - Field(u'class-id', u'short', reserved=False), - Field(u'method-id', u'short', reserved=False), + Field(u'reply-code', u'reply-code', u'short', reserved=False), + Field(u'reply-text', u'reply-text', u'shortstr', reserved=False), + Field(u'class-id', u'class-id', u'short', reserved=False), + Field(u'method-id', u'method-id', u'short', reserved=False), ] def __init__(self, reply_code, reply_text, class_id, method_id): @@ -187,12 +188,10 @@ class ConnectionCloseOk(AMQPMethodPayload): BINARY_HEADER = b'\x0A\x33' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = True # this means that argument part has always the same content STATIC_CONTENT = b'\x00\x00\x00\x04\x0A\x33\xCE' # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END - RESPONSE_TO = ConnectionClose # this is sent in response to connection.close def __init__(self): """ @@ -221,16 +220,15 @@ class ConnectionOpen(AMQPMethodPayload): BINARY_HEADER = b'\x0A\x28' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [ConnectionOpenOk] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'virtual-host', u'shortstr', reserved=False), - Field(u'reserved-1', u'shortstr', reserved=True), - Field(u'reserved-2', u'bit', reserved=True), + Field(u'virtual-host', u'path', u'shortstr', reserved=False), + Field(u'reserved-1', u'shortstr', u'shortstr', reserved=True), + Field(u'reserved-2', u'bit', u'bit', reserved=True), ] def __init__(self, virtual_host): @@ -279,16 +277,14 @@ class ConnectionOpenOk(AMQPMethodPayload): BINARY_HEADER = b'\x0A\x29' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = True # this means that argument part has always the same content STATIC_CONTENT = b'\x00\x00\x00\x04\x0A\x29\x00\xCE' # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END - RESPONSE_TO = ConnectionOpen # this is sent in response to connection.open # See constructor pydoc for details FIELDS = [ - Field(u'reserved-1', u'shortstr', reserved=True), + Field(u'reserved-1', u'shortstr', u'shortstr', reserved=True), ] def __init__(self): @@ -320,18 +316,17 @@ class ConnectionStart(AMQPMethodPayload): BINARY_HEADER = b'\x0A\x0A' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [ConnectionStartOk] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'version-major', u'octet', reserved=False), - Field(u'version-minor', u'octet', reserved=False), - Field(u'server-properties', u'table', reserved=False), - Field(u'mechanisms', u'longstr', reserved=False), - Field(u'locales', u'longstr', reserved=False), + Field(u'version-major', u'octet', u'octet', reserved=False), + Field(u'version-minor', u'octet', u'octet', reserved=False), + Field(u'server-properties', u'peer-properties', u'table', reserved=False), + Field(u'mechanisms', u'longstr', u'longstr', reserved=False), + Field(u'locales', u'longstr', u'longstr', reserved=False), ] def __init__(self, version_major, version_minor, server_properties, mechanisms, locales): @@ -407,14 +402,13 @@ class ConnectionSecure(AMQPMethodPayload): BINARY_HEADER = b'\x0A\x14' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [ConnectionSecureOk] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'challenge', u'longstr', reserved=False), + Field(u'challenge', u'longstr', u'longstr', reserved=False), ] def __init__(self, challenge): @@ -457,18 +451,16 @@ class ConnectionStartOk(AMQPMethodPayload): BINARY_HEADER = b'\x0A\x0B' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content - RESPONSE_TO = ConnectionStart # this is sent in response to connection.start # See constructor pydoc for details FIELDS = [ - Field(u'client-properties', u'table', reserved=False), - Field(u'mechanism', u'shortstr', reserved=False), - Field(u'response', u'longstr', reserved=False), - Field(u'locale', u'shortstr', reserved=False), + Field(u'client-properties', u'peer-properties', u'table', reserved=False), + Field(u'mechanism', u'shortstr', u'shortstr', reserved=False), + Field(u'response', u'longstr', u'longstr', reserved=False), + Field(u'locale', u'shortstr', u'shortstr', reserved=False), ] def __init__(self, client_properties, mechanism, response, locale): @@ -544,15 +536,13 @@ class ConnectionSecureOk(AMQPMethodPayload): BINARY_HEADER = b'\x0A\x15' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content - RESPONSE_TO = ConnectionSecure # this is sent in response to connection.secure # See constructor pydoc for details FIELDS = [ - Field(u'response', u'longstr', reserved=False), + Field(u'response', u'longstr', u'longstr', reserved=False), ] def __init__(self, response): @@ -596,16 +586,15 @@ class ConnectionTune(AMQPMethodPayload): BINARY_HEADER = b'\x0A\x1E' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [ConnectionTuneOk] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'channel-max', u'short', reserved=False), - Field(u'frame-max', u'long', reserved=False), - Field(u'heartbeat', u'short', reserved=False), + Field(u'channel-max', u'short', u'short', reserved=False), + Field(u'frame-max', u'long', u'long', reserved=False), + Field(u'heartbeat', u'short', u'short', reserved=False), ] def __init__(self, channel_max, frame_max, heartbeat): @@ -658,17 +647,15 @@ class ConnectionTuneOk(AMQPMethodPayload): BINARY_HEADER = b'\x0A\x1F' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content - RESPONSE_TO = ConnectionTune # this is sent in response to connection.tune # See constructor pydoc for details FIELDS = [ - Field(u'channel-max', u'short', reserved=False), - Field(u'frame-max', u'long', reserved=False), - Field(u'heartbeat', u'short', reserved=False), + Field(u'channel-max', u'short', u'short', reserved=False), + Field(u'frame-max', u'long', u'long', reserved=False), + Field(u'heartbeat', u'short', u'short', reserved=False), ] def __init__(self, channel_max, frame_max, heartbeat): @@ -733,17 +720,16 @@ class ChannelClose(AMQPMethodPayload): BINARY_HEADER = b'\x14\x28' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, True - REPLY_WITH = [ChannelCloseOk] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'reply-code', u'short', reserved=False), - Field(u'reply-text', u'shortstr', reserved=False), - Field(u'class-id', u'short', reserved=False), - Field(u'method-id', u'short', reserved=False), + Field(u'reply-code', u'reply-code', u'short', reserved=False), + Field(u'reply-text', u'reply-text', u'shortstr', reserved=False), + Field(u'class-id', u'class-id', u'short', reserved=False), + Field(u'method-id', u'method-id', u'short', reserved=False), ] def __init__(self, reply_code, reply_text, class_id, method_id): @@ -798,12 +784,10 @@ class ChannelCloseOk(AMQPMethodPayload): BINARY_HEADER = b'\x14\x29' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = True # this means that argument part has always the same content STATIC_CONTENT = b'\x00\x00\x00\x04\x14\x29\xCE' # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END - RESPONSE_TO = ChannelClose # this is sent in response to channel.close def __init__(self): """ @@ -833,14 +817,13 @@ class ChannelFlow(AMQPMethodPayload): BINARY_HEADER = b'\x14\x14' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, True - REPLY_WITH = [ChannelFlowOk] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'active', u'bit', reserved=False), + Field(u'active', u'bit', u'bit', reserved=False), ] def __init__(self, active): @@ -881,15 +864,13 @@ class ChannelFlowOk(AMQPMethodPayload): BINARY_HEADER = b'\x14\x15' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content - RESPONSE_TO = ChannelFlow # this is sent in response to channel.flow # See constructor pydoc for details FIELDS = [ - Field(u'active', u'bit', reserved=False), + Field(u'active', u'bit', u'bit', reserved=False), ] def __init__(self, active): @@ -930,7 +911,6 @@ class ChannelOpen(AMQPMethodPayload): BINARY_HEADER = b'\x14\x0A' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [ChannelOpenOk] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = True # this means that argument part has always the same content @@ -938,7 +918,7 @@ class ChannelOpen(AMQPMethodPayload): # See constructor pydoc for details FIELDS = [ - Field(u'reserved-1', u'shortstr', reserved=True), + Field(u'reserved-1', u'shortstr', u'shortstr', reserved=True), ] def __init__(self): @@ -968,16 +948,14 @@ class ChannelOpenOk(AMQPMethodPayload): BINARY_HEADER = b'\x14\x0B' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = True # this means that argument part has always the same content STATIC_CONTENT = b'\x00\x00\x00\x05\x14\x0B\x00\x00\x00\x00\xCE' # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END - RESPONSE_TO = ChannelOpen # this is sent in response to channel.open # See constructor pydoc for details FIELDS = [ - Field(u'reserved-1', u'longstr', reserved=True), + Field(u'reserved-1', u'longstr', u'longstr', reserved=True), ] def __init__(self): @@ -1018,22 +996,21 @@ class ExchangeDeclare(AMQPMethodPayload): BINARY_HEADER = b'\x28\x0A' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [ExchangeDeclareOk] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'reserved-1', u'short', reserved=True), - Field(u'exchange', u'shortstr', reserved=False), - Field(u'type', u'shortstr', reserved=False), - Field(u'passive', u'bit', reserved=False), - Field(u'durable', u'bit', reserved=False), - Field(u'reserved-2', u'bit', reserved=True), - Field(u'reserved-3', u'bit', reserved=True), - Field(u'no-wait', u'bit', reserved=False), - Field(u'arguments', u'table', reserved=False), + Field(u'reserved-1', u'short', u'short', reserved=True), + Field(u'exchange', u'exchange-name', u'shortstr', reserved=False), + Field(u'type', u'shortstr', u'shortstr', reserved=False), + Field(u'passive', u'bit', u'bit', reserved=False), + Field(u'durable', u'bit', u'bit', reserved=False), + Field(u'reserved-2', u'bit', u'bit', reserved=True), + Field(u'reserved-3', u'bit', u'bit', reserved=True), + Field(u'no-wait', u'no-wait', u'bit', reserved=False), + Field(u'arguments', u'table', u'table', reserved=False), ] def __init__(self, exchange, type_, passive, durable, no_wait, arguments): @@ -1121,17 +1098,16 @@ class ExchangeDelete(AMQPMethodPayload): BINARY_HEADER = b'\x28\x14' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [ExchangeDeleteOk] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'reserved-1', u'short', reserved=True), - Field(u'exchange', u'shortstr', reserved=False), - Field(u'if-unused', u'bit', reserved=False), - Field(u'no-wait', u'bit', reserved=False), + Field(u'reserved-1', u'short', u'short', reserved=True), + Field(u'exchange', u'exchange-name', u'shortstr', reserved=False), + Field(u'if-unused', u'bit', u'bit', reserved=False), + Field(u'no-wait', u'no-wait', u'bit', reserved=False), ] def __init__(self, exchange, if_unused, no_wait): @@ -1186,12 +1162,10 @@ class ExchangeDeclareOk(AMQPMethodPayload): BINARY_HEADER = b'\x28\x0B' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = True # this means that argument part has always the same content STATIC_CONTENT = b'\x00\x00\x00\x04\x28\x0B\xCE' # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END - RESPONSE_TO = ExchangeDeclare # this is sent in response to exchange.declare def __init__(self): """ @@ -1217,12 +1191,10 @@ class ExchangeDeleteOk(AMQPMethodPayload): BINARY_HEADER = b'\x28\x15' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = True # this means that argument part has always the same content STATIC_CONTENT = b'\x00\x00\x00\x04\x28\x15\xCE' # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END - RESPONSE_TO = ExchangeDelete # this is sent in response to exchange.delete def __init__(self): """ @@ -1262,19 +1234,18 @@ class QueueBind(AMQPMethodPayload): BINARY_HEADER = b'\x32\x14' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [QueueBindOk] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'reserved-1', u'short', reserved=True), - Field(u'queue', u'shortstr', reserved=False), - Field(u'exchange', u'shortstr', reserved=False), - Field(u'routing-key', u'shortstr', reserved=False), - Field(u'no-wait', u'bit', reserved=False), - Field(u'arguments', u'table', reserved=False), + Field(u'reserved-1', u'short', u'short', reserved=True), + Field(u'queue', u'queue-name', u'shortstr', reserved=False), + Field(u'exchange', u'exchange-name', u'shortstr', reserved=False), + Field(u'routing-key', u'shortstr', u'shortstr', reserved=False), + Field(u'no-wait', u'no-wait', u'bit', reserved=False), + Field(u'arguments', u'table', u'table', reserved=False), ] def __init__(self, queue, exchange, routing_key, no_wait, arguments): @@ -1356,12 +1327,10 @@ class QueueBindOk(AMQPMethodPayload): BINARY_HEADER = b'\x32\x15' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = True # this means that argument part has always the same content STATIC_CONTENT = b'\x00\x00\x00\x04\x32\x15\xCE' # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END - RESPONSE_TO = QueueBind # this is sent in response to queue.bind def __init__(self): """ @@ -1389,21 +1358,20 @@ class QueueDeclare(AMQPMethodPayload): BINARY_HEADER = b'\x32\x0A' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [QueueDeclareOk] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'reserved-1', u'short', reserved=True), - Field(u'queue', u'shortstr', reserved=False), - Field(u'passive', u'bit', reserved=False), - Field(u'durable', u'bit', reserved=False), - Field(u'exclusive', u'bit', reserved=False), - Field(u'auto-delete', u'bit', reserved=False), - Field(u'no-wait', u'bit', reserved=False), - Field(u'arguments', u'table', reserved=False), + Field(u'reserved-1', u'short', u'short', reserved=True), + Field(u'queue', u'queue-name', u'shortstr', reserved=False), + Field(u'passive', u'bit', u'bit', reserved=False), + Field(u'durable', u'bit', u'bit', reserved=False), + Field(u'exclusive', u'bit', u'bit', reserved=False), + Field(u'auto-delete', u'bit', u'bit', reserved=False), + Field(u'no-wait', u'no-wait', u'bit', reserved=False), + Field(u'arguments', u'table', u'table', reserved=False), ] def __init__(self, queue, passive, durable, exclusive, auto_delete, no_wait, arguments): @@ -1496,18 +1464,17 @@ class QueueDelete(AMQPMethodPayload): BINARY_HEADER = b'\x32\x28' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [QueueDeleteOk] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'reserved-1', u'short', reserved=True), - Field(u'queue', u'shortstr', reserved=False), - Field(u'if-unused', u'bit', reserved=False), - Field(u'if-empty', u'bit', reserved=False), - Field(u'no-wait', u'bit', reserved=False), + Field(u'reserved-1', u'short', u'short', reserved=True), + Field(u'queue', u'queue-name', u'shortstr', reserved=False), + Field(u'if-unused', u'bit', u'bit', reserved=False), + Field(u'if-empty', u'bit', u'bit', reserved=False), + Field(u'no-wait', u'no-wait', u'bit', reserved=False), ] def __init__(self, queue, if_unused, if_empty, no_wait): @@ -1567,17 +1534,15 @@ class QueueDeclareOk(AMQPMethodPayload): BINARY_HEADER = b'\x32\x0B' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content - RESPONSE_TO = QueueDeclare # this is sent in response to queue.declare # See constructor pydoc for details FIELDS = [ - Field(u'queue', u'shortstr', reserved=False), - Field(u'message-count', u'long', reserved=False), - Field(u'consumer-count', u'long', reserved=False), + Field(u'queue', u'queue-name', u'shortstr', reserved=False), + Field(u'message-count', u'message-count', u'long', reserved=False), + Field(u'consumer-count', u'long', u'long', reserved=False), ] def __init__(self, queue, message_count, consumer_count): @@ -1629,15 +1594,13 @@ class QueueDeleteOk(AMQPMethodPayload): BINARY_HEADER = b'\x32\x29' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content - RESPONSE_TO = QueueDelete # this is sent in response to queue.delete # See constructor pydoc for details FIELDS = [ - Field(u'message-count', u'long', reserved=False), + Field(u'message-count', u'message-count', u'long', reserved=False), ] def __init__(self, message_count): @@ -1676,16 +1639,15 @@ class QueuePurge(AMQPMethodPayload): BINARY_HEADER = b'\x32\x1E' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [QueuePurgeOk] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'reserved-1', u'short', reserved=True), - Field(u'queue', u'shortstr', reserved=False), - Field(u'no-wait', u'bit', reserved=False), + Field(u'reserved-1', u'short', u'short', reserved=True), + Field(u'queue', u'queue-name', u'shortstr', reserved=False), + Field(u'no-wait', u'no-wait', u'bit', reserved=False), ] def __init__(self, queue, no_wait): @@ -1732,15 +1694,13 @@ class QueuePurgeOk(AMQPMethodPayload): BINARY_HEADER = b'\x32\x1F' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content - RESPONSE_TO = QueuePurge # this is sent in response to queue.purge # See constructor pydoc for details FIELDS = [ - Field(u'message-count', u'long', reserved=False), + Field(u'message-count', u'message-count', u'long', reserved=False), ] def __init__(self, message_count): @@ -1778,18 +1738,17 @@ class QueueUnbind(AMQPMethodPayload): BINARY_HEADER = b'\x32\x32' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [QueueUnbindOk] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'reserved-1', u'short', reserved=True), - Field(u'queue', u'shortstr', reserved=False), - Field(u'exchange', u'shortstr', reserved=False), - Field(u'routing-key', u'shortstr', reserved=False), - Field(u'arguments', u'table', reserved=False), + Field(u'reserved-1', u'short', u'short', reserved=True), + Field(u'queue', u'queue-name', u'shortstr', reserved=False), + Field(u'exchange', u'exchange-name', u'shortstr', reserved=False), + Field(u'routing-key', u'shortstr', u'shortstr', reserved=False), + Field(u'arguments', u'table', u'table', reserved=False), ] def __init__(self, queue, exchange, routing_key, arguments): @@ -1856,12 +1815,10 @@ class QueueUnbindOk(AMQPMethodPayload): BINARY_HEADER = b'\x32\x33' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = True # this means that argument part has always the same content STATIC_CONTENT = b'\x00\x00\x00\x04\x32\x33\xCE' # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END - RESPONSE_TO = QueueUnbind # this is sent in response to queue.unbind def __init__(self): """ @@ -1888,21 +1845,90 @@ class BasicContentPropertyList(AMQPContentPropertyList): The basic class provides methods that support an industry-standard messaging model. """ FIELDS = [ - Field(u'content-type', u'shortstr', False), - Field(u'content-encoding', u'shortstr', False), - Field(u'headers', u'table', False), - Field(u'delivery-mode', u'octet', False), - Field(u'priority', u'octet', False), - Field(u'correlation-id', u'shortstr', False), - Field(u'reply-to', u'shortstr', False), - Field(u'expiration', u'shortstr', False), - Field(u'message-id', u'shortstr', False), - Field(u'timestamp', u'timestamp', False), - Field(u'type', u'shortstr', False), - Field(u'user-id', u'shortstr', False), - Field(u'app-id', u'shortstr', False), - Field(u'reserved', u'shortstr', False), + Field(u'content-type', u'shortstr', u'shortstr', False), + Field(u'content-encoding', u'shortstr', u'shortstr', False), + Field(u'headers', u'table', u'table', False), + Field(u'delivery-mode', u'octet', u'octet', False), + Field(u'priority', u'octet', u'octet', False), + Field(u'correlation-id', u'shortstr', u'shortstr', False), + Field(u'reply-to', u'shortstr', u'shortstr', False), + Field(u'expiration', u'shortstr', u'shortstr', False), + Field(u'message-id', u'shortstr', u'shortstr', False), + Field(u'timestamp', u'timestamp', u'timestamp', False), + Field(u'type', u'shortstr', u'shortstr', False), + Field(u'user-id', u'shortstr', u'shortstr', False), + Field(u'app-id', u'shortstr', u'shortstr', False), + Field(u'reserved', u'shortstr', u'shortstr', False), ] + # A dictionary from a zero property list to a class typized with + # some fields + PARTICULAR_CLASSES = {} + + def __new__(self, **kwargs): + """ + Return a property list. + :param content_type: MIME content type + :type content_type: binary type (max length 255) (AMQP as shortstr) + :param content_encoding: MIME content encoding + :type content_encoding: binary type (max length 255) (AMQP as shortstr) + :param headers: message header field table + :type headers: table. See coolamqp.uplink.framing.field_table (AMQP as table) + :param delivery_mode: non-persistent (1) or persistent (2) + :type delivery_mode: int, 8 bit unsigned (AMQP as octet) + :param priority: message priority, 0 to 9 + :type priority: int, 8 bit unsigned (AMQP as octet) + :param correlation_id: application correlation identifier + :type correlation_id: binary type (max length 255) (AMQP as shortstr) + :param reply_to: address to reply to + :type reply_to: binary type (max length 255) (AMQP as shortstr) + :param expiration: message expiration specification + :type expiration: binary type (max length 255) (AMQP as shortstr) + :param message_id: application message identifier + :type message_id: binary type (max length 255) (AMQP as shortstr) + :param timestamp: message timestamp + :type timestamp: 64 bit signed POSIX timestamp (in seconds) (AMQP as timestamp) + :param type_: message type name + :type type_: binary type (max length 255) (AMQP as shortstr) + :param user_id: creating user id + :type user_id: binary type (max length 255) (AMQP as shortstr) + :param app_id: creating application id + :type app_id: binary type (max length 255) (AMQP as shortstr) + :param reserved: reserved, must be empty + :type reserved: binary type (max length 255) (AMQP as shortstr) + """ + zpf = bytearray([ + (('content_type' in kwargs) << 7) | (('content_encoding' in kwargs) << 6) | (('headers' in kwargs) << 5) | (('delivery_mode' in kwargs) << 4) | (('priority' in kwargs) << 3) | (('correlation_id' in kwargs) << 2) | (('reply_to' in kwargs) << 1) | int('expiration' in kwargs), + (('message_id' in kwargs) << 7) | (('timestamp' in kwargs) << 6) | (('type_' in kwargs) << 5) | (('user_id' in kwargs) << 4) | (('app_id' in kwargs) << 3) | (('reserved' in kwargs) << 2) + ]) + zpf = six.binary_type(zpf) + + if zpf in BasicContentPropertyList.PARTICULAR_CLASSES: + warnings.warn(u"""You could go faster. + + If you know in advance what properties you will be using, use typized constructors like + + # runs once + my_type = BasicContentPropertyList.typize('content_type', 'content_encoding') + # runs many times + props = my_type('text/plain', 'utf8') + + instead of + + # runs many times + props = BasicContentPropertyList(content_type='text/plain', content_encoding='utf8') + + This way you will be faster. + + If you do not know in advance what properties you will be using, it is correct to use + this constructor. + """) + + return BasicContentPropertyList.PARTICULAR_CLASSES[zpf](**kwargs) + else: + logger.debug('Property field (BasicContentPropertyList:%s) not seen yet, compiling', repr(zpf)) + c = compile_particular_content_property_list_class(zpf, BasicContentPropertyList.FIELDS) + BasicContentPropertyList.PARTICULAR_CLASSES[zpf] = c + return c(**kwargs) class BasicAck(AMQPMethodPayload): @@ -1919,15 +1945,14 @@ class BasicAck(AMQPMethodPayload): BINARY_HEADER = b'\x3C\x50' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'delivery-tag', u'longlong', reserved=False), - Field(u'multiple', u'bit', reserved=False), + Field(u'delivery-tag', u'delivery-tag', u'longlong', reserved=False), + Field(u'multiple', u'bit', u'bit', reserved=False), ] def __init__(self, delivery_tag, multiple): @@ -1976,21 +2001,20 @@ class BasicConsume(AMQPMethodPayload): BINARY_HEADER = b'\x3C\x14' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [BasicConsumeOk] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'reserved-1', u'short', reserved=True), - Field(u'queue', u'shortstr', reserved=False), - Field(u'consumer-tag', u'shortstr', reserved=False), - Field(u'no-local', u'bit', reserved=False), - Field(u'no-ack', u'bit', reserved=False), - Field(u'exclusive', u'bit', reserved=False), - Field(u'no-wait', u'bit', reserved=False), - Field(u'arguments', u'table', reserved=False), + Field(u'reserved-1', u'short', u'short', reserved=True), + Field(u'queue', u'queue-name', u'shortstr', reserved=False), + Field(u'consumer-tag', u'consumer-tag', u'shortstr', reserved=False), + Field(u'no-local', u'no-local', u'bit', reserved=False), + Field(u'no-ack', u'no-ack', u'bit', reserved=False), + Field(u'exclusive', u'bit', u'bit', reserved=False), + Field(u'no-wait', u'no-wait', u'bit', reserved=False), + Field(u'arguments', u'table', u'table', reserved=False), ] def __init__(self, queue, consumer_tag, no_local, no_ack, exclusive, no_wait, arguments): @@ -2071,15 +2095,14 @@ class BasicCancel(AMQPMethodPayload): BINARY_HEADER = b'\x3C\x1E' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [BasicCancelOk] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'consumer-tag', u'shortstr', reserved=False), - Field(u'no-wait', u'bit', reserved=False), + Field(u'consumer-tag', u'consumer-tag', u'shortstr', reserved=False), + Field(u'no-wait', u'no-wait', u'bit', reserved=False), ] def __init__(self, consumer_tag, no_wait): @@ -2126,15 +2149,13 @@ class BasicConsumeOk(AMQPMethodPayload): BINARY_HEADER = b'\x3C\x15' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content - RESPONSE_TO = BasicConsume # this is sent in response to basic.consume # See constructor pydoc for details FIELDS = [ - Field(u'consumer-tag', u'shortstr', reserved=False), + Field(u'consumer-tag', u'consumer-tag', u'shortstr', reserved=False), ] def __init__(self, consumer_tag): @@ -2175,15 +2196,13 @@ class BasicCancelOk(AMQPMethodPayload): BINARY_HEADER = b'\x3C\x1F' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content - RESPONSE_TO = BasicCancel # this is sent in response to basic.cancel # See constructor pydoc for details FIELDS = [ - Field(u'consumer-tag', u'shortstr', reserved=False), + Field(u'consumer-tag', u'consumer-tag', u'shortstr', reserved=False), ] def __init__(self, consumer_tag): @@ -2226,18 +2245,17 @@ class BasicDeliver(AMQPMethodPayload): BINARY_HEADER = b'\x3C\x3C' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'consumer-tag', u'shortstr', reserved=False), - Field(u'delivery-tag', u'longlong', reserved=False), - Field(u'redelivered', u'bit', reserved=False), - Field(u'exchange', u'shortstr', reserved=False), - Field(u'routing-key', u'shortstr', reserved=False), + Field(u'consumer-tag', u'consumer-tag', u'shortstr', reserved=False), + Field(u'delivery-tag', u'delivery-tag', u'longlong', reserved=False), + Field(u'redelivered', u'redelivered', u'bit', reserved=False), + Field(u'exchange', u'exchange-name', u'shortstr', reserved=False), + Field(u'routing-key', u'shortstr', u'shortstr', reserved=False), ] def __init__(self, consumer_tag, delivery_tag, redelivered, exchange, routing_key): @@ -2306,16 +2324,15 @@ class BasicGet(AMQPMethodPayload): BINARY_HEADER = b'\x3C\x46' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [BasicGetOk, BasicGetEmpty] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'reserved-1', u'short', reserved=True), - Field(u'queue', u'shortstr', reserved=False), - Field(u'no-ack', u'bit', reserved=False), + Field(u'reserved-1', u'short', u'short', reserved=True), + Field(u'queue', u'queue-name', u'shortstr', reserved=False), + Field(u'no-ack', u'no-ack', u'bit', reserved=False), ] def __init__(self, queue, no_ack): @@ -2364,19 +2381,17 @@ class BasicGetOk(AMQPMethodPayload): BINARY_HEADER = b'\x3C\x47' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content - RESPONSE_TO = BasicGet # this is sent in response to basic.get # See constructor pydoc for details FIELDS = [ - Field(u'delivery-tag', u'longlong', reserved=False), - Field(u'redelivered', u'bit', reserved=False), - Field(u'exchange', u'shortstr', reserved=False), - Field(u'routing-key', u'shortstr', reserved=False), - Field(u'message-count', u'long', reserved=False), + Field(u'delivery-tag', u'delivery-tag', u'longlong', reserved=False), + Field(u'redelivered', u'redelivered', u'bit', reserved=False), + Field(u'exchange', u'exchange-name', u'shortstr', reserved=False), + Field(u'routing-key', u'shortstr', u'shortstr', reserved=False), + Field(u'message-count', u'message-count', u'long', reserved=False), ] def __init__(self, delivery_tag, redelivered, exchange, routing_key, message_count): @@ -2441,16 +2456,14 @@ class BasicGetEmpty(AMQPMethodPayload): BINARY_HEADER = b'\x3C\x48' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = True # this means that argument part has always the same content STATIC_CONTENT = b'\x00\x00\x00\x0D\x3C\x48\x00\xCE' # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END - RESPONSE_TO = BasicGet # this is sent in response to basic.get # See constructor pydoc for details FIELDS = [ - Field(u'reserved-1', u'shortstr', reserved=True), + Field(u'reserved-1', u'shortstr', u'shortstr', reserved=True), ] def __init__(self): @@ -2482,18 +2495,17 @@ class BasicPublish(AMQPMethodPayload): BINARY_HEADER = b'\x3C\x28' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'reserved-1', u'short', reserved=True), - Field(u'exchange', u'shortstr', reserved=False), - Field(u'routing-key', u'shortstr', reserved=False), - Field(u'mandatory', u'bit', reserved=False), - Field(u'immediate', u'bit', reserved=False), + Field(u'reserved-1', u'short', u'short', reserved=True), + Field(u'exchange', u'exchange-name', u'shortstr', reserved=False), + Field(u'routing-key', u'shortstr', u'shortstr', reserved=False), + Field(u'mandatory', u'bit', u'bit', reserved=False), + Field(u'immediate', u'bit', u'bit', reserved=False), ] def __init__(self, exchange, routing_key, mandatory, immediate): @@ -2569,16 +2581,15 @@ class BasicQos(AMQPMethodPayload): BINARY_HEADER = b'\x3C\x0A' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [BasicQosOk] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'prefetch-size', u'long', reserved=False), - Field(u'prefetch-count', u'short', reserved=False), - Field(u'global', u'bit', reserved=False), + Field(u'prefetch-size', u'long', u'long', reserved=False), + Field(u'prefetch-count', u'short', u'short', reserved=False), + Field(u'global', u'bit', u'bit', reserved=False), ] def __init__(self, prefetch_size, prefetch_count, global_): @@ -2641,12 +2652,10 @@ class BasicQosOk(AMQPMethodPayload): BINARY_HEADER = b'\x3C\x0B' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = True # this means that argument part has always the same content STATIC_CONTENT = b'\x00\x00\x00\x04\x3C\x0B\xCE' # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END - RESPONSE_TO = BasicQos # this is sent in response to basic.qos def __init__(self): """ @@ -2675,17 +2684,16 @@ class BasicReturn(AMQPMethodPayload): BINARY_HEADER = b'\x3C\x32' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = False # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'reply-code', u'short', reserved=False), - Field(u'reply-text', u'shortstr', reserved=False), - Field(u'exchange', u'shortstr', reserved=False), - Field(u'routing-key', u'shortstr', reserved=False), + Field(u'reply-code', u'reply-code', u'short', reserved=False), + Field(u'reply-text', u'reply-text', u'shortstr', reserved=False), + Field(u'exchange', u'exchange-name', u'shortstr', reserved=False), + Field(u'routing-key', u'shortstr', u'shortstr', reserved=False), ] def __init__(self, reply_code, reply_text, exchange, routing_key): @@ -2749,15 +2757,14 @@ class BasicReject(AMQPMethodPayload): BINARY_HEADER = b'\x3C\x5A' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'delivery-tag', u'longlong', reserved=False), - Field(u'requeue', u'bit', reserved=False), + Field(u'delivery-tag', u'delivery-tag', u'longlong', reserved=False), + Field(u'requeue', u'bit', u'bit', reserved=False), ] def __init__(self, delivery_tag, requeue): @@ -2804,14 +2811,13 @@ class BasicRecoverAsync(AMQPMethodPayload): BINARY_HEADER = b'\x3C\x64' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'requeue', u'bit', reserved=False), + Field(u'requeue', u'bit', u'bit', reserved=False), ] def __init__(self, requeue): @@ -2855,14 +2861,13 @@ class BasicRecover(AMQPMethodPayload): BINARY_HEADER = b'\x3C\x6E' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = False # this means that argument part has always the same content # See constructor pydoc for details FIELDS = [ - Field(u'requeue', u'bit', reserved=False), + Field(u'requeue', u'bit', u'bit', reserved=False), ] def __init__(self, requeue): @@ -2904,7 +2909,6 @@ class BasicRecoverOk(AMQPMethodPayload): BINARY_HEADER = b'\x3C\x6F' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = True # this means that argument part has always the same content @@ -2952,7 +2956,6 @@ class TxCommit(AMQPMethodPayload): BINARY_HEADER = b'\x5A\x14' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [TxCommitOk] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = True # this means that argument part has always the same content @@ -2983,12 +2986,10 @@ class TxCommitOk(AMQPMethodPayload): BINARY_HEADER = b'\x5A\x15' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = True # this means that argument part has always the same content STATIC_CONTENT = b'\x00\x00\x00\x04\x5A\x15\xCE' # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END - RESPONSE_TO = TxCommit # this is sent in response to tx.commit def __init__(self): """ @@ -3017,7 +3018,6 @@ class TxRollback(AMQPMethodPayload): BINARY_HEADER = b'\x5A\x1E' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [TxRollbackOk] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = True # this means that argument part has always the same content @@ -3048,12 +3048,10 @@ class TxRollbackOk(AMQPMethodPayload): BINARY_HEADER = b'\x5A\x1F' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = True # this means that argument part has always the same content STATIC_CONTENT = b'\x00\x00\x00\x04\x5A\x1F\xCE' # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END - RESPONSE_TO = TxRollback # this is sent in response to tx.rollback def __init__(self): """ @@ -3080,7 +3078,6 @@ class TxSelect(AMQPMethodPayload): BINARY_HEADER = b'\x5A\x0A' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = True, False - REPLY_WITH = [TxSelectOk] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = True # this means that argument part has always the same content @@ -3111,12 +3108,10 @@ class TxSelectOk(AMQPMethodPayload): BINARY_HEADER = b'\x5A\x0B' # CLASS ID + METHOD ID SENT_BY_CLIENT, SENT_BY_SERVER = False, True - REPLY_WITH = [] # methods you can reply with to this one IS_SIZE_STATIC = True # this means that argument part has always the same length IS_CONTENT_STATIC = True # this means that argument part has always the same content STATIC_CONTENT = b'\x00\x00\x00\x04\x5A\x0B\xCE' # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END - RESPONSE_TO = TxSelect # this is sent in response to tx.select def __init__(self): """ @@ -3248,3 +3243,90 @@ CLASS_ID_TO_CONTENT_PROPERTY_LIST = { 60: BasicContentPropertyList, } +# Methods that are sent as replies to other methods, ie. ConnectionOpenOk: ConnectionOpen +# if a method is NOT a reply, it will not be in this dict +# a method may be a reply for AT MOST one method +REPLY_REASONS_FOR = { + BasicGetEmpty: BasicGet, + BasicGetOk: BasicGet, + ExchangeDeleteOk: ExchangeDelete, + TxSelectOk: TxSelect, + QueueBindOk: QueueBind, + BasicConsumeOk: BasicConsume, + BasicCancelOk: BasicCancel, + TxRollbackOk: TxRollback, + ChannelOpenOk: ChannelOpen, + QueueDeleteOk: QueueDelete, + ChannelCloseOk: ChannelClose, + BasicQosOk: BasicQos, + ConnectionStartOk: ConnectionStart, + QueueUnbindOk: QueueUnbind, + TxCommitOk: TxCommit, + QueuePurgeOk: QueuePurge, + QueueDeclareOk: QueueDeclare, + ExchangeDeclareOk: ExchangeDeclare, + ConnectionTuneOk: ConnectionTune, + ConnectionSecureOk: ConnectionSecure, + ConnectionOpenOk: ConnectionOpen, + ChannelFlowOk: ChannelFlow, + ConnectionCloseOk: ConnectionClose, +} + +# Methods that are replies for other, ie. ConnectionOpenOk: ConnectionOpen +# a method may be a reply for ONE or NONE other methods +# if a method has no replies, it will have an empty list as value here +REPLIES_FOR= { + BasicGetEmpty: [], + BasicRecoverOk: [], + BasicReturn: [], + QueueDeclare: [QueueDeclareOk], + BasicGetOk: [], + ConnectionSecure: [ConnectionSecureOk], + ExchangeDeleteOk: [], + TxRollback: [TxRollbackOk], + TxSelectOk: [], + QueueBindOk: [], + ChannelFlow: [ChannelFlowOk], + BasicConsumeOk: [], + BasicRecover: [], + BasicCancelOk: [], + BasicGet: [BasicGetOk, BasicGetEmpty], + TxRollbackOk: [], + BasicAck: [], + ExchangeDelete: [ExchangeDeleteOk], + BasicConsume: [BasicConsumeOk], + ConnectionClose: [ConnectionCloseOk], + ChannelOpenOk: [], + QueueDeleteOk: [], + QueueBind: [QueueBindOk], + ConnectionStart: [ConnectionStartOk], + BasicQos: [BasicQosOk], + QueueUnbind: [QueueUnbindOk], + BasicQosOk: [], + BasicReject: [], + ChannelCloseOk: [], + ExchangeDeclare: [ExchangeDeclareOk], + BasicPublish: [], + ConnectionTune: [ConnectionTuneOk], + ConnectionStartOk: [], + QueueUnbindOk: [], + QueueDelete: [QueueDeleteOk], + ConnectionCloseOk: [], + QueuePurge: [QueuePurgeOk], + ChannelOpen: [ChannelOpenOk], + ChannelClose: [ChannelCloseOk], + QueuePurgeOk: [], + QueueDeclareOk: [], + BasicCancel: [BasicCancelOk], + ExchangeDeclareOk: [], + TxCommitOk: [], + ConnectionTuneOk: [], + ConnectionSecureOk: [], + ConnectionOpenOk: [], + ChannelFlowOk: [], + BasicRecoverAsync: [], + TxSelect: [TxSelectOk], + BasicDeliver: [], + TxCommit: [TxCommitOk], + ConnectionOpen: [ConnectionOpenOk], +} diff --git a/coolamqp/framing/frames.py b/coolamqp/framing/frames.py index dad0c7dc06d39dd105dc65135a7c7a2fd10740d5..3f8e9bbca52d232ca88f7656ea4413477dc59233 100644 --- a/coolamqp/framing/frames.py +++ b/coolamqp/framing/frames.py @@ -52,8 +52,8 @@ class AMQPHeaderFrame(AMQPFrame): :param class_id: class ID :param weight: weight (lol wut?) :param body_size: size of the body to follow - :param property_flags: - :param property_list: + :param property_flags: binary + :param property_list: a list of properties """ AMQPFrame.__init__(self, channel) self.class_id = class_id @@ -64,7 +64,9 @@ class AMQPHeaderFrame(AMQPFrame): def write_to(self, buf): AMQPFrame.write_to(self, buf) - buf.write(struct.pack('!HHQH')) + buf.write(struct.pack('!HHQ', self.class_id, 0, self.body_size)) + buf.write(self.property_flags) + @staticmethod def unserialize(channel, payload_as_buffer): diff --git a/setup.py b/setup.py index 4b387b77bd470ba383663fd152dad00d7bb927f1..94c0c3c419fdaedf1c032fc45d47242864dfd47b 100644 --- a/setup.py +++ b/setup.py @@ -2,6 +2,7 @@ # coding=UTF-8 from setuptools import setup + setup(name='CoolAMQP', version='0.12', description='AMQP client with sane reconnects', diff --git a/tests/test_framing/__init__.py b/tests/test_framing/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9f2b35b38d89264ee25685611d0a65a192e165f6 --- /dev/null +++ b/tests/test_framing/__init__.py @@ -0,0 +1,2 @@ +# coding=UTF-8 +from __future__ import absolute_import, division, print_function diff --git a/tests/test_framing/test_definitions/__init__.py b/tests/test_framing/test_definitions/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9f2b35b38d89264ee25685611d0a65a192e165f6 --- /dev/null +++ b/tests/test_framing/test_definitions/__init__.py @@ -0,0 +1,2 @@ +# coding=UTF-8 +from __future__ import absolute_import, division, print_function diff --git a/tests/test_framing/test_definitions/test_cpl.py b/tests/test_framing/test_definitions/test_cpl.py new file mode 100644 index 0000000000000000000000000000000000000000..03b8e9f12830f200aa25a6cc9823acb856ee4acf --- /dev/null +++ b/tests/test_framing/test_definitions/test_cpl.py @@ -0,0 +1,32 @@ +# coding=UTF-8 +from __future__ import absolute_import, division, print_function +import unittest +import io + + +from coolamqp.framing.definitions import BasicContentPropertyList + + +class TestBasicContentPropertyList(unittest.TestCase): + def test_bcpl1(self): + bcpl = BasicContentPropertyList(content_type='text/plain', content_encoding='utf8') + + self.assertEquals(bcpl.content_type, 'text/plain') + self.assertEquals(bcpl.content_encoding, 'utf8') + + buf = io.BytesIO() + bcpl.write_to(buf) + + ser = buf.getvalue() + self.assertEquals(ser, '\xC0\x00' + chr(len('text/plain')) + b'text/plain\x04utf8') + + def test_bcpl2(self): + bcpl = BasicContentPropertyList(content_type='text/plain') + + self.assertEquals(bcpl.content_type, 'text/plain') + + buf = io.BytesIO() + bcpl.write_to(buf) + + ser = buf.getvalue() + self.assertEquals(ser, '\x80\x00' + chr(len('text/plain')) + b'text/plain')