diff --git a/coolamqp/__init__.py b/coolamqp/__init__.py index 64f889284517fbb688deda2350210370ee5285ff..95995627150479d0550211c778e6688909beca06 100644 --- a/coolamqp/__init__.py +++ b/coolamqp/__init__.py @@ -1,6 +1 @@ # coding=UTF-8 -from coolamqp.cluster import ClusterNode, Cluster -from coolamqp.events import ConnectionDown, ConnectionUp, MessageReceived, ConsumerCancelled -from coolamqp.messages import Message, Exchange, Queue -from coolamqp.backends.base import Cancelled, Discarded - diff --git a/coolamqp/uplink/authentication.py b/coolamqp/cluster/__init__.py similarity index 70% rename from coolamqp/uplink/authentication.py rename to coolamqp/cluster/__init__.py index 7d799de9fe82a818cfa13db8bc8bc411c8cd7feb..9f2b35b38d89264ee25685611d0a65a192e165f6 100644 --- a/coolamqp/uplink/authentication.py +++ b/coolamqp/cluster/__init__.py @@ -1,4 +1,2 @@ # coding=UTF-8 -"""All things authentication""" from __future__ import absolute_import, division, print_function - diff --git a/coolamqp/connection/__init__.py b/coolamqp/connection/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9f2b35b38d89264ee25685611d0a65a192e165f6 --- /dev/null +++ b/coolamqp/connection/__init__.py @@ -0,0 +1,2 @@ +# coding=UTF-8 +from __future__ import absolute_import, division, print_function diff --git a/coolamqp/uplink/frames/__init__.py b/coolamqp/framing/__init__.py similarity index 59% rename from coolamqp/uplink/frames/__init__.py rename to coolamqp/framing/__init__.py index dd8748f57c4aeca1bb21fce23d0cc98c3fc73d8b..7e413d8fc8f284c7d85e772ebf519058ee1af9b8 100644 --- a/coolamqp/uplink/frames/__init__.py +++ b/coolamqp/framing/__init__.py @@ -2,8 +2,8 @@ from __future__ import absolute_import, division, print_function """ -Definitions of frames. -Mechanisms for serialization/deserialization of AMQP frames and other types. +Definitions of framing. +Mechanisms for serialization/deserialization of AMQP framing and other types. definitions.py is machine-generated from AMQP specification. diff --git a/coolamqp/uplink/frames/base.py b/coolamqp/framing/base.py similarity index 55% rename from coolamqp/uplink/frames/base.py rename to coolamqp/framing/base.py index 7cd16e14983164343621704dbcd4bcfc2a8a733a..6ddeddf3e8f5d79a3079492ed240351d0b42ff37 100644 --- a/coolamqp/uplink/frames/base.py +++ b/coolamqp/framing/base.py @@ -1,8 +1,8 @@ # coding=UTF-8 from __future__ import absolute_import, division, print_function -import struct + import logging -import six +import struct logger = logging.getLogger(__name__) @@ -16,7 +16,7 @@ BASIC_TYPES = {'bit': (None, None, "0", None), # special case 'short': (2, 'H', "b'\\x00\\x00'", 2), 'long': (4, 'I', "b'\\x00\\x00\\x00\\x00'", 4), 'longlong': (8, 'Q', "b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'", 8), - 'timestamp': (8, 'L', "b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'", 8), + 'timestamp': (8, 'Q', "b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'", 8), 'table': (None, None, "b'\\x00\\x00\\x00\\x00'", 4), # special case 'longstr': (None, None, "b'\\x00\\x00\\x00\\x00'", 4), # special case 'shortstr': (None, None, "b'\\x00'", 1), # special case @@ -25,8 +25,7 @@ BASIC_TYPES = {'bit': (None, None, "0", None), # special case DYNAMIC_BASIC_TYPES = ('table', 'longstr', 'shortstr') - -class AMQPFrame(object): # base class for frames +class AMQPFrame(object): # base class for framing FRAME_TYPE = None # override me! def __init__(self, channel): @@ -75,3 +74,68 @@ class AMQPPayload(object): :return: int """ raise NotImplementedError() + + +class AMQPClass(object): + """An AMQP class""" + + +class AMQPContentPropertyList(object): + """ + A class is intmately bound with content and content properties + """ + PROPERTIES = [] + + +class AMQPMethodPayload(AMQPPayload): + RESPONSE_TO = None + REPLY_WITH = [] + FIELDS = [] + + def write_to(self, buf): + """ + Write own content to target buffer - starting from LENGTH, ending on FRAME_END + :param buf: target buffer + """ + from coolamqp.framing import FRAME_END + + if self.IS_CONTENT_STATIC: + buf.write(self.STATIC_CONTENT) + else: + buf.write(struct.pack('!I', self.get_size()+2)) + buf.write(self.BINARY_HEADER) + self.write_arguments(buf) + buf.write(chr(FRAME_END)) + + def get_size(self): + """ + Calculate the size of this frame. + + :return: int, size of argument section + """ + raise NotImplementedError() + + def write_arguments(self, buf): + """ + Write the argument portion of this frame into buffer. + + :param buf: buffer to write to + :return: how many bytes written + :raise ValueError: some field here is invalid! + """ + raise NotImplementedError() + + @staticmethod + def from_buffer(buf, offset): + """ + Construct this frame from a buffer + + :param buf: a buffer to construct the frame from + :type buf: buffer or memoryview + :param offset: offset the argument portion begins at + :type offset: int + :return: tuple of (an instance of %s, amount of bytes consumed as int) + :raise ValueError: invalid data + """ + raise NotImplementedError('') + diff --git a/coolamqp/uplink/frames/compilation/__init__.py b/coolamqp/framing/compilation/__init__.py similarity index 100% rename from coolamqp/uplink/frames/compilation/__init__.py rename to coolamqp/framing/compilation/__init__.py diff --git a/coolamqp/framing/compilation/compile_definitions.py b/coolamqp/framing/compilation/compile_definitions.py new file mode 100644 index 0000000000000000000000000000000000000000..1d2f91ee5aa7b39fd95a0ce3ef6a69716070b335 --- /dev/null +++ b/coolamqp/framing/compilation/compile_definitions.py @@ -0,0 +1,276 @@ +from __future__ import division + +import collections +import math +import struct +from xml.etree import ElementTree + +import six +from coolamqp.framing.base import BASIC_TYPES + +from coolamqp.framing.compilation.utilities import get_constants, get_classes, get_domains, \ + name_class, format_method_class_name, format_field_name, ffmt, to_docstring, pythonify_name, to_code_binary, \ + frepr, get_size + +TYPE_TRANSLATOR = { + 'shortstr': 'binary type (max length 255)', + 'longstr': 'binary type', + 'table': 'table. See coolamqp.uplink.framing.field_table', + 'bit': 'bool', + 'octet': 'int, 8 bit unsigned', + 'short': 'int, 16 bit unsigned', + 'long': 'int, 32 bit unsigned', + 'longlong': 'int, 64 bit unsigned', + 'timestamp': '64 bit signed POSIX timestamp (in seconds)', +} + +def compile_definitions(xml_file='resources/amqp0-9-1.xml', out_file='coolamqp/framing/definitions.py'): + """parse resources/amqp-0-9-1.xml into """ + + xml = ElementTree.parse(xml_file) + out = open(out_file, 'wb') + + out.write('''# coding=UTF-8 +from __future__ import print_function, absolute_import +""" +A Python version of the AMQP machine-readable specification. + +Generated automatically by CoolAMQP from AMQP machine-readable specification. +See coolamqp.uplink.framing.compilation for the tool + +AMQP is copyright (c) 2016 OASIS +CoolAMQP is copyright (c) 2016 DMS Serwis s.c. +""" + +import struct +import collections + +from coolamqp.framing.base_definitions import AMQPClass, AMQPMethodPayload, AMQPContentPropertyList +from coolamqp.framing.field_table import enframe_table, deframe_table, frame_table_size + +Field = collections.namedtuple('Field', ('name', 'type', 'reserved')) + +''') + + def line(data, *args, **kwargs): + out.write(ffmt(data, *args, sane=True)) + + # Output core ones + FRAME_END = None + con_classes = collections.defaultdict(list) + line('# Core constants\n') + for constant in get_constants(xml): + if pythonify_name(constant.name) == 'FRAME_END': + FRAME_END = constant.value + g = ffmt('%s = %s', pythonify_name(constant.name), constant.value) + line(g) + if constant.docs: + lines = constant.docs.split('\n') + line(' # %s\n', lines[0]) + if len(lines) > 1: + for ln in lines[1:]: + line(u' '*len(g)) + line(u' # %s\n', ln) + else: + line('\n') + + if constant.kind: + con_classes[constant.kind].append(pythonify_name(constant.name)) + + for constant_kind, constants in con_classes.items(): + line('\n%s = [%s]', pythonify_name(constant_kind), u', '.join(constants)) + + # get domains + domain_to_basic_type = {} + line('\n\n\nDOMAIN_TO_BASIC_TYPE = {\n') + for domain in get_domains(xml): + line(u' %s: %s,\n', frepr(domain.name), frepr(None if domain.elementary else domain.type)) + domain_to_basic_type[domain.name] = domain.type + + line('}\n') + + class_id_to_contentpropertylist = {} + + # Output classes + for cls in get_classes(xml): + + cls = cls._replace(properties=[p._replace(basic_type=domain_to_basic_type[p.type]) for p in cls.properties]) + + line('''\nclass %s(AMQPClass): + """ + %s + """ + NAME = %s + INDEX = %s + +''', + name_class(cls.name), to_docstring(None, cls.docs), frepr(cls.name), cls.index) + + if len(cls.properties) > 0: + class_id_to_contentpropertylist[cls.index] = name_class(cls.name)+'ContentPropertyList' + + line('''\nclass %sContentPropertyList(AMQPContentPropertyList): + """ + %s + """ + FIELDS = [ +''', + + name_class(cls.name), to_docstring(None, cls.docs), frepr(cls.name), cls.index, name_class(cls.name)) + + 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') + + + + + + + + # ============================================ Do methods for this class + for method in cls.methods: + full_class_name = '%s%s' % (name_class(cls.name), format_method_class_name(method.name)) + + # annotate types + method.fields = [field._replace(basic_type=domain_to_basic_type[field.type]) for field in method.fields] + + is_static = method.is_static() + if is_static: + static_size = get_size(method.fields) + + is_content_static = len([f for f in method.fields if not f.reserved]) == 0 + + line('''\nclass %s(AMQPMethodPayload): + """ + %s + """ + NAME = %s + + INDEX = (%s, %s) # (Class ID, Method ID) + 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 +''', + full_class_name, + to_docstring(method.label, method.docs), + frepr(cls.name + '.' + method.name), + frepr(cls.index), frepr(method.index), + 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) + ) + + + if is_content_static: + + line(''' STATIC_CONTENT = %s # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END +''', + to_code_binary(struct.pack('!LBB', static_size + 4, cls.index, method.index) + \ + 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(' ]\n') + + non_reserved_fields = [field for field in method.fields if not field.reserved] + + # constructor + line('''\n def __init__(%s): + """ + Create frame %s +''', + u', '.join(['self'] + [format_field_name(field.name) for field in non_reserved_fields]), + cls.name + '.' + method.name, + ) + + 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), + to_docstring(field.label, field.docs, prefix=12, blank=False)) + + + + line(' :type %s: %s (%s in AMQP)\n', format_field_name(field.name), TYPE_TRANSLATOR[field.basic_type], field.type) + + line(' """\n') + + for field in non_reserved_fields: + line(' self.%s = %s\n', format_field_name(field.name), format_field_name(field.name)) + + if len(non_reserved_fields) == 0: + line('\n') + + # end + if not is_content_static: + from coolamqp.framing.compilation.textcode_fields import get_serializer, get_counter, get_from_buffer + line('\n def write_arguments(self, buf):\n') + line(get_serializer(method.fields, 'self.', 2)) + + line(' def get_size(self):\n') + line(get_counter(method.fields, 'self.', 2)) + + line('''\n @staticmethod + def from_buffer(buf, start_offset): + offset = start_offset +''') + + line(get_from_buffer(method.fields, '', 2)) + line(" return %s(%s)", + full_class_name, + u', '.join(format_field_name(field.name) for field in method.fields if not field.reserved)) + + line('\n\n') + + # Get me a dict - (classid, methodid) => class of method + dct = {} + for cls in get_classes(xml): + for method in cls.methods: + dct[((cls.index, method.index))] = '%s%s' % (name_class(cls.name), format_method_class_name(method.name)) + + line('\nIDENT_TO_METHOD = {\n') + for k, v in dct.items(): + line(' %s: %s,\n', repr(k), v) + line('}\n\n') + + line('\nBINARY_HEADER_TO_METHOD = {\n') + for k, v in dct.items(): + line(' %s: %s,\n', to_code_binary(struct.pack('!BB', *k)), v) + line('}\n\n') + + line('\nCLASS_ID_TO_CONTENT_PROPERTY_LIST = {\n') + for k,v in class_id_to_contentpropertylist.items(): + line(' %s: %s,\n', k, v) + line('}\n\n') + + + out.close() + + +if __name__ == '__main__': + compile_definitions() diff --git a/coolamqp/framing/compilation/content_property.py b/coolamqp/framing/compilation/content_property.py new file mode 100644 index 0000000000000000000000000000000000000000..ab85174b937600be8bb6fe49926fe0f996e898c6 --- /dev/null +++ b/coolamqp/framing/compilation/content_property.py @@ -0,0 +1,27 @@ +# coding=UTF-8 +from __future__ import absolute_import, division, print_function +""" +Generate serializers/unserializers/length getters for given property_flags +""" + +class ContentPropertyListCompiler(object): + """ + This produces serializers, unserializers and size getters for any property_flags combinations. + + Sure you could do that by hand, but how much faster is it to have most common messages precompiled? + """ + + MAX_COMPILERS_TO_KEEP = 8 + + def __init__(self, content_property_list_class): + + +def compile_for(fields): + """Compile a serializer, unserializer and length calculator for a list of fields""" + + 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 diff --git a/coolamqp/framing/compilation/textcode_fields.py b/coolamqp/framing/compilation/textcode_fields.py new file mode 100644 index 0000000000000000000000000000000000000000..551183f80592c3a0ed53df49d1e5d3b23bbcf99e --- /dev/null +++ b/coolamqp/framing/compilation/textcode_fields.py @@ -0,0 +1,225 @@ +# coding=UTF-8 +""" +Return Python code used to serialize/unserialize/get_size of lists of fields. + +If you are going to paste the code you get here, note you nede to paste it into +a module that has following ok: + + * local variables denoted with a list of Field (namedtuple) with optional prefix exist + * function header was already emitted + * indent_level is in multiple of fours + * following imports exists: + * import struct + * from coolamqp.framing.field_table import enframe_table, deframe_table, frame_table_size + * local variables buf and offset exist + * local variable start_offset can be created + +""" +from __future__ import absolute_import, division, print_function +import math + +from coolamqp.framing.base import BASIC_TYPES, DYNAMIC_BASIC_TYPES +from coolamqp.framing.compilation.utilities import format_field_name, get_size + +def get_counter(fields, prefix='', indent_level=2): + """ + Emit code that counts how long this struct is. + + :param fields: list of Field instances + :param prefix: pass "self." is inside a class + :param indent_level: amount of tabs + :return: block of code that does that + """ + + parts = [] + accumulator = 0 + bits = 0 + for field in fields: + bt = field.basic_type + nam = prefix+format_field_name(field.name) + + if (bits > 0) and (bt != 'bit'): # sync bits if not + accumulator += int(math.ceil(bits / 8)) + bits = 0 + + if field.basic_type == 'bit': + bits += 1 + elif field.reserved: + accumulator += BASIC_TYPES[field.basic_type][3] + elif BASIC_TYPES[bt][0] is not None: + accumulator += BASIC_TYPES[field.basic_type][0] + elif bt == 'shortstr': + parts.append('len('+nam+')') + accumulator += 1 + elif bt == 'longstr': + parts.append('len(' + nam + ')') + accumulator += 4 + elif bt == 'table': + parts.append('frame_table_size(' + nam + ')') + accumulator += 4 + else: + raise Exception() + + if bits > 0: # sync bits + accumulator += int(math.ceil(bits / 8)) + + return (u' '*indent_level)+u'return '+(u' + '.join([str(accumulator)]+parts))+u'\n' + + +def get_from_buffer(fields, prefix='', indent_level=2): + """ + Emit code that collects values from buf:offset, updating offset as progressing. + """ + code = [] + def emit(fmt, *args): + args = list(args) + code.append(u' '*indent_level) + assert fmt.count('%s') == len(args) + for arg in args: + fmt = fmt.replace('%s', str(arg), 1) + code.append(fmt) + code.append('\n') + + + # actually go and load it + + bits = [] + ln = {'ln': 0} # so I can modify from outside + to_struct = [] + + def emit_bits(): + if all(n == '_' for n in bits): + # everything is reserved, lol + emit('offset += 1') + else: + emit("_bit, = struct.unpack_from('!B', buf, offset)") + + for multiplier, bit in enumerate(bits): + if bit != '_': + emit("%s = bool(_bit >> %s)", bit, multiplier) + emit('offset += 1') + + del bits[:] + + def emit_structures(): + fffnames = [a for a, b in to_struct if a != u'_'] # skip reserved + ffffmts = [b for a, b in to_struct] + emit("%s, = struct.unpack_from('!%s', buf, offset)", u', '.join(fffnames), u''.join(ffffmts)) + emit("offset += %s", ln['ln']) + ln['ln'] = 0 + del to_struct[:] + + for field in fields: + fieldname = prefix+format_field_name(field.name) + + if (len(bits) > 0) and (field.basic_type != 'bit'): + emit_bits() + + # offset is current start + # length is length to read + if BASIC_TYPES[field.basic_type][0] is not None: + # static type shit has + if field.reserved: + to_struct.append((u'_', '%sx' % (BASIC_TYPES[field.basic_type][0],))) + else: + to_struct.append((fieldname, BASIC_TYPES[field.basic_type][1])) + ln['ln'] += BASIC_TYPES[field.basic_type][0] + elif field.basic_type == u'bit': + bits.append('_' if field.reserved else fieldname) + elif field.basic_type == u'table': # oh my god + emit("%s, delta = deframe_table(buf, offset)", fieldname) + emit("offset += delta") + else: # longstr or shortstr + f_q, f_l = ('L', 4) if field.basic_type == u'longstr' else ('B', 1) + to_struct.append(('s_len', f_q)) + ln['ln'] += f_l + emit_structures() + if field.reserved: + emit("offset += s_len # reserved field!") + else: + emit("%s = buf[offset:offset+s_len]", fieldname) + emit("offset += s_len") + + # check bits for overflow + if len(bits) == 8: + emit_bits() + + if len(bits) > 0: + emit_bits() + if len(to_struct) > 0: + emit_structures() + + return u''.join(code) + + +def get_serializer(fields, prefix='', indent_level=2): + """ + Emit code that serializes the fields into buf at offset + + :param fields: list of Field instances + :param prefix: pass "self." is inside a class + :return: block of code that does that + """ + code = [] + + def emit(fmt, *args): + args = list(args) + code.append(u' '*indent_level) + while len(args) > 0: + fmt = fmt.replace('%s', args[0], 1) + del args[0] + code.append(fmt) + code.append('\n') + + formats = [] + format_args = [] + bits = [] + + def emit_bits(): + p = [] + formats.append('B') + if all(bit_name == 'False' for bit_name in bits): + format_args.append('0') + else: + for bit_name, modif in zip(bits, range(8)): + if bit_name != 'False': + p.append('('+bit_name+' << %s)' % (modif, )) # yes you can << bools + format_args.append(u' | '.join(p)) + del bits[:] + + def emit_single_struct_pack(): + emit("buf.write(struct.pack('!%s', %s))", u''.join(formats), u', '.join(format_args)) + del formats[:] + del format_args[:] + + for field in fields: + nam = prefix+format_field_name(field.name) + + if (len(bits) == 8) or ((len(bits) > 0) and field.basic_type != 'bit'): + emit_bits() + + if field.basic_type == 'bit': + if field.reserved: + bits.append("False") + else: + bits.append(nam) + elif field.basic_type in ('shortstr', 'longstr'): + formats.append('B' if field.basic_type == 'shortstr' else 'I') + format_args.append('len('+nam+')') + emit_single_struct_pack() + emit('buf.write(%s)', nam) + elif field.basic_type == 'table': + emit('enframe_table(buf, %s)', nam) + else: + formats.append(BASIC_TYPES[field.basic_type][1]) + format_args.append(nam) + + if len(bits) > 0: + emit_bits() + if len(formats) > 0: + emit_single_struct_pack() + + emit('') # eol + + return u''.join(code) + diff --git a/coolamqp/uplink/frames/compilation/utilities.py b/coolamqp/framing/compilation/utilities.py similarity index 77% rename from coolamqp/uplink/frames/compilation/utilities.py rename to coolamqp/framing/compilation/utilities.py index 90c8c22cd48ebcda23e8338c79a15cbda12e552b..f847007c2a2ccdf9f53b4be492210815fb6fda99 100644 --- a/coolamqp/uplink/frames/compilation/utilities.py +++ b/coolamqp/framing/compilation/utilities.py @@ -1,25 +1,27 @@ # coding=UTF-8 from __future__ import absolute_import, division, print_function + +import math from collections import namedtuple + import six -import math -from coolamqp.uplink.frames.base import BASIC_TYPES, DYNAMIC_BASIC_TYPES +from coolamqp.framing.base import BASIC_TYPES, DYNAMIC_BASIC_TYPES # docs may be None + + Constant = namedtuple('Constant', ('name', 'value', 'kind', 'docs')) # kind is AMQP constant class # value is int Field = namedtuple('Field', ('name', 'type', 'label', 'docs', 'reserved', 'basic_type')) # reserved is bool Method = namedtuple('Method', ('name', 'synchronous', 'index', 'label', 'docs', 'fields', 'response', 'sent_by_client', 'sent_by_server', 'constant')) # synchronous is bool, constant is bool # repponse is a list of method.name -Property = namedtuple('Property', ('name', 'type', 'label', 'basic_type', 'reserved')) -Class_ = namedtuple('Class_', ('name', 'index', 'docs', 'methods', 'content_properties')) # label is int +Class_ = namedtuple('Class_', ('name', 'index', 'docs', 'methods', 'properties')) # label is int Domain = namedtuple('Domain', ('name', 'type', 'elementary')) # elementary is bool - class Method(object): def __init__(self, name, synchronous, index, label, docs, fields, response, sent_by_client, sent_by_server): self.name = name @@ -49,36 +51,34 @@ class Method(object): body.append(eval(BASIC_TYPES[field.basic_type][2])) return b''.join(body) - def get_size(self, domain_to_type=None): # for static methods - size = 0 - bits = 0 + def is_static(self, domain_to_type=None): # is size constant? for field in self.fields: + if field.basic_type in DYNAMIC_BASIC_TYPES: + return False + return True - if (bits > 0) and (field.basic_type != 'bit'): # sync bits - size += int(math.ceil(bits / 8)) - bits = 0 - if BASIC_TYPES[field.basic_type][0] is None: - if field.basic_type == 'bit': - bits += 1 - else: - size += len(BASIC_TYPES[field.basic_type][2]) # default minimum entry - else: - size += BASIC_TYPES[field.basic_type][0] +def get_size(fields): # assume all fields have static length + size = 0 + bits = 0 + for field in fields: - if bits > 0: # sync bits + if (bits > 0) and (field.basic_type != 'bit'): # sync bits size += int(math.ceil(bits / 8)) + bits = 0 - return size + if BASIC_TYPES[field.basic_type][0] is None: + if field.basic_type == 'bit': + bits += 1 + else: + size += len(BASIC_TYPES[field.basic_type][2]) # default minimum entry + else: + size += BASIC_TYPES[field.basic_type][0] - def is_static(self, domain_to_type=None): # is size constant? - for field in self.fields: - if field.basic_type in DYNAMIC_BASIC_TYPES: - return False - return True + if bits > 0: # sync bits + size += int(math.ceil(bits / 8)) - def get_minimum_size(self, domain_to_type=None): - return self.get_size() + return size def get_docs(elem): @@ -98,7 +98,7 @@ def for_domain(elem): return Domain(six.text_type(a['name']), a['type'], a['type'] == a['name']) -def for_method_field(elem): # for <field> in <method> +def for_field(elem): # for <field> in <method> a = elem.attrib return Field(six.text_type(a['name']), a['domain'] if 'domain' in a else a['type'], a.get('label', None), @@ -106,16 +106,10 @@ def for_method_field(elem): # for <field> in <method> a.get('reserved', '0') == '1', None) - -def for_content_property(elem): - a = elem.attrib - return Property(a['name'], a['domain'], a.get('label', ''), None, 'reserved' in a['name']) - - def for_method(elem): # for <method> a = elem.attrib return Method(six.text_type(a['name']), bool(int(a.get('synchronous', '0'))), int(a['index']), a['label'], get_docs(elem), - [for_method_field(fie) for fie in elem.getchildren() if fie.tag == 'field'], + [for_field(fie) for fie in elem.getchildren() if fie.tag == 'field'], [e.attrib['name'] for e in elem.findall('response')], # if chassis=server that means server has to accept it any([e.attrib.get('name', '') == 'server' for e in elem.getchildren() if e.tag == 'chassis']), @@ -126,7 +120,7 @@ def for_class(elem): # for <class> a = elem.attrib methods = sorted([for_method(me) for me in elem.getchildren() if me.tag == 'method'], key=lambda m: (m.name.strip('-')[0], -len(m.response))) return Class_(six.text_type(a['name']), int(a['index']), get_docs(elem) or a['label'], methods, - [for_content_property(e) for e in elem.getchildren() if e.tag == 'field']) + [for_field(e) for e in elem.getchildren() if e.tag == 'field']) def for_constant(elem): # for <constant> a = elem.attrib @@ -143,30 +137,31 @@ def get_domains(xml): return [for_domain(e) for e in xml.findall('domain')] -def a_text(callable): +def as_unicode(callable): def roll(*args, **kwargs): return six.text_type(callable(*args, **kwargs)) return roll -def byname(list_of_things): +def to_dict_by_name(list_of_things): return dict((a.name, a) for a in list_of_things) -@a_text +@as_unicode def name_class(classname): """Change AMQP class name to Python class name""" return classname.capitalize() -@a_text -def name_method(methodname): +@as_unicode +def format_method_class_name(methodname): if '-' in methodname: i = methodname.find('-') return methodname[0:i].capitalize() + methodname[i+1].upper() + methodname[i+2:] else: return methodname.capitalize() -@a_text -def name_field(field): - if field in ('global', ): +@as_unicode +def format_field_name(field): + print(repr(field)) + if field in (u'global', u'type'): field = field + '_' return field.replace('-', '_') @@ -180,7 +175,7 @@ def frepr(p, sop=six.text_type): else: return s -def as_nice_escaped_string(p): +def to_code_binary(p): body = [] for q in p: z = (hex(ord(q))[2:].upper()) @@ -189,16 +184,16 @@ def as_nice_escaped_string(p): body.append(u'\\x' + z) return u"b'"+(u''.join(body))+u"'" -def normname(p): +def pythonify_name(p): return p.strip().replace('-', '_').upper() -def infertype(p): +def try_to_int(p): try: return int(p) except ValueError: return p -def doxify(label, doc, prefix=4, blank=True): # output a full docstring section +def to_docstring(label, doc, prefix=4, blank=True): # output a full docstring section label = [] if label is None else [label] doc = [] if doc is None else [q.strip() for q in doc.split(u'\n') if len(q.strip()) > 0] pre = u' '*prefix diff --git a/coolamqp/uplink/frames/definitions.py b/coolamqp/framing/definitions.py similarity index 59% rename from coolamqp/uplink/frames/definitions.py rename to coolamqp/framing/definitions.py index b369497deb3f23328e48fd98efdcbf54cdfda80a..c7a5d57839c0a89f24b81bc132d7952263ccac11 100644 --- a/coolamqp/uplink/frames/definitions.py +++ b/coolamqp/framing/definitions.py @@ -4,16 +4,19 @@ from __future__ import print_function, absolute_import A Python version of the AMQP machine-readable specification. Generated automatically by CoolAMQP from AMQP machine-readable specification. -See coolamqp.uplink.frames.compilation for the tool +See coolamqp.uplink.framing.compilation for the tool AMQP is copyright (c) 2016 OASIS CoolAMQP is copyright (c) 2016 DMS Serwis s.c. """ import struct +import collections -from coolamqp.uplink.frames.base_definitions import AMQPClass, AMQPMethodPayload, AMQPContentPropertyList -from coolamqp.uplink.frames.field_table import enframe_table, deframe_table, frame_table_size +from coolamqp.framing.base_definitions import AMQPClass, AMQPMethodPayload, AMQPContentPropertyList +from coolamqp.framing.field_table import enframe_table, deframe_table, frame_table_size + +Field = collections.namedtuple('Field', ('name', 'type', 'reserved')) # Core constants FRAME_METHOD = 1 @@ -104,37 +107,6 @@ class Connection(AMQPClass): INDEX = 10 -class ConnectionContentPropertyList(AMQPContentPropertyList): - """ - The connection class provides methods for a client to establish a network connection to - - a server, and for both peers to operate the connection thereafter. - """ - CLASS_NAME = u'connection' - CLASS_INDEX = 10 - CLASS = Connection - - CONTENT_PROPERTIES = [ # tuple of (name, domain, type) - - ] - - def __init__(self): - """ - Create the property list. - - """ - - def write_arguments(self, buf): - pass # this has a frame, but its only default shortstrs - - @staticmethod - def from_buffer(buf, start_offset): - return ConnectionContentPropertyList() - - def get_size(self): - return 0 - - class ConnectionClose(AMQPMethodPayload): """ Request a connection close @@ -144,34 +116,23 @@ class ConnectionClose(AMQPMethodPayload): a specific method, i.e. an exception. When a close is due to an exception, the sender provides the class and method id of the method which caused the exception. """ - CLASS = Connection - NAME = u'close' - CLASSNAME = u'connection' - FULLNAME = u'connection.close' + NAME = u'connection.close' - CONTENT_PROPERTY_LIST = ConnectionContentPropertyList - - CLASS_INDEX = 10 - CLASS_INDEX_BINARY = b'\x0A' - METHOD_INDEX = 50 - METHOD_INDEX_BINARY = b'\x32' + INDEX = (10, 50) # (Class ID, Method ID) BINARY_HEADER = b'\x0A\x32' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [ConnectionCloseOk] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = True - - MINIMUM_SIZE = 13 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'reply-code', u'reply-code', u'short', False), - (u'reply-text', u'reply-text', u'shortstr', False), - (u'class-id', u'class-id', u'short', False), # failing method class - (u'method-id', u'method-id', u'short', False), # failing method ID + + # 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), ] def __init__(self, reply_code, reply_text, class_id, method_id): @@ -197,14 +158,13 @@ class ConnectionClose(AMQPMethodPayload): buf.write(struct.pack('!HB', self.reply_code, len(self.reply_text))) buf.write(self.reply_text) buf.write(struct.pack('!HH', self.class_id, self.method_id)) - + def get_size(self): - return len(self.reply_text) + 7 + return 7 + len(self.reply_text) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= ConnectionClose.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset reply_code, s_len, = struct.unpack_from('!HB', buf, offset) offset += 3 reply_text = buf[offset:offset+s_len] @@ -221,26 +181,13 @@ class ConnectionCloseOk(AMQPMethodPayload): This method confirms a Connection.Close method and tells the recipient that it is safe to release resources for the connection and close the socket. """ - CLASS = Connection - NAME = u'close-ok' - CLASSNAME = u'connection' - FULLNAME = u'connection.close-ok' + NAME = u'connection.close-ok' - CONTENT_PROPERTY_LIST = ConnectionContentPropertyList - - CLASS_INDEX = 10 - CLASS_INDEX_BINARY = b'\x0A' - METHOD_INDEX = 51 - METHOD_INDEX_BINARY = b'\x33' + INDEX = (10, 51) # (Class ID, Method ID) BINARY_HEADER = b'\x0A\x33' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = True - - MINIMUM_SIZE = 0 # arguments part can never be shorter than this + 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 @@ -252,10 +199,10 @@ class ConnectionCloseOk(AMQPMethodPayload): Create frame connection.close-ok """ - # not generating write_arguments - this method has static content! @staticmethod def from_buffer(buf, start_offset): + offset = start_offset return ConnectionCloseOk() @@ -268,33 +215,22 @@ class ConnectionOpen(AMQPMethodPayload): The server may apply arbitrary limits per virtual host, such as the number of each type of entity that may be used, per connection and/or in total. """ - CLASS = Connection - NAME = u'open' - CLASSNAME = u'connection' - FULLNAME = u'connection.open' - - CONTENT_PROPERTY_LIST = ConnectionContentPropertyList + NAME = u'connection.open' - CLASS_INDEX = 10 - CLASS_INDEX_BINARY = b'\x0A' - METHOD_INDEX = 40 - METHOD_INDEX_BINARY = b'\x28' + INDEX = (10, 40) # (Class ID, Method ID) BINARY_HEADER = b'\x0A\x28' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [ConnectionOpenOk] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 15 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'virtual-host', u'path', u'shortstr', False), # virtual host name - (u'reserved-1', u'shortstr', u'shortstr', True), - (u'reserved-2', u'bit', u'bit', True), + + # 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), ] def __init__(self, virtual_host): @@ -310,23 +246,23 @@ class ConnectionOpen(AMQPMethodPayload): def write_arguments(self, buf): buf.write(struct.pack('!B', len(self.virtual_host))) buf.write(self.virtual_host) - buf.write(b'\x00') - buf.write(struct.pack('!B', )) - + buf.write(struct.pack('!B', len(self.reserved_1))) + buf.write(self.reserved_1) + buf.write(struct.pack('!B', 0)) + def get_size(self): - return len(self.virtual_host) + 3 + return 3 + len(self.virtual_host) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= ConnectionOpen.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset s_len, = struct.unpack_from('!B', buf, offset) offset += 1 virtual_host = buf[offset:offset+s_len] offset += s_len s_len, = struct.unpack_from('!B', buf, offset) offset += 1 - offset += s_len + offset += s_len # reserved field! offset += 1 return ConnectionOpen(virtual_host) @@ -337,33 +273,22 @@ class ConnectionOpenOk(AMQPMethodPayload): This method signals to the client that the connection is ready for use. """ - CLASS = Connection - NAME = u'open-ok' - CLASSNAME = u'connection' - FULLNAME = u'connection.open-ok' + NAME = u'connection.open-ok' - CONTENT_PROPERTY_LIST = ConnectionContentPropertyList - - CLASS_INDEX = 10 - CLASS_INDEX_BINARY = b'\x0A' - METHOD_INDEX = 41 - METHOD_INDEX_BINARY = b'\x29' + INDEX = (10, 41) # (Class ID, Method ID) BINARY_HEADER = b'\x0A\x29' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 7 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'reserved-1', u'shortstr', u'shortstr', True), + + # See constructor pydoc for details + FIELDS = [ + Field(u'reserved-1', u'shortstr', reserved=True), ] def __init__(self): @@ -371,10 +296,13 @@ class ConnectionOpenOk(AMQPMethodPayload): Create frame connection.open-ok """ - # not generating write_arguments - this method has static content! @staticmethod def from_buffer(buf, start_offset): + offset = start_offset + s_len, = struct.unpack_from('!B', buf, offset) + offset += 1 + offset += s_len # reserved field! return ConnectionOpenOk() @@ -386,35 +314,24 @@ class ConnectionStart(AMQPMethodPayload): protocol version that the server proposes, along with a list of security mechanisms which the client can use for authentication. """ - CLASS = Connection - NAME = u'start' - CLASSNAME = u'connection' - FULLNAME = u'connection.start' - - CONTENT_PROPERTY_LIST = ConnectionContentPropertyList + NAME = u'connection.start' - CLASS_INDEX = 10 - CLASS_INDEX_BINARY = b'\x0A' - METHOD_INDEX = 10 - METHOD_INDEX_BINARY = b'\x0A' + INDEX = (10, 10) # (Class ID, Method ID) BINARY_HEADER = b'\x0A\x0A' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [ConnectionStartOk] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 59 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'version-major', u'octet', u'octet', False), # protocol major version - (u'version-minor', u'octet', u'octet', False), # protocol minor version - (u'server-properties', u'peer-properties', u'table', False), # server properties - (u'mechanisms', u'longstr', u'longstr', False), # available security mechanisms - (u'locales', u'longstr', u'longstr', False), # available message locales + + # 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), ] def __init__(self, version_major, version_minor, server_properties, mechanisms, locales): @@ -435,7 +352,7 @@ class ConnectionStart(AMQPMethodPayload): "version", giving the name of the server version, "platform", giving the name of the operating system, "copyright", if appropriate, and "information", giving other general information. - :type server_properties: table. See coolamqp.uplink.frames.field_table (peer-properties in AMQP) + :type server_properties: table. See coolamqp.uplink.framing.field_table (peer-properties in AMQP) :param mechanisms: Available security mechanisms A list of the security mechanisms that the server supports, delimited by spaces. :type mechanisms: binary type (longstr in AMQP) @@ -451,20 +368,18 @@ class ConnectionStart(AMQPMethodPayload): self.locales = locales def write_arguments(self, buf): - buf.write(struct.pack('!BB', self.version_major, self.version_minor)) enframe_table(buf, self.server_properties) - buf.write(struct.pack('!L', len(self.mechanisms))) + buf.write(struct.pack('!BBI', self.version_major, self.version_minor, len(self.mechanisms))) buf.write(self.mechanisms) - buf.write(struct.pack('!L', len(self.locales))) + buf.write(struct.pack('!I', len(self.locales))) buf.write(self.locales) - + def get_size(self): - return frame_table_size(self.server_properties) + len(self.mechanisms) + len(self.locales) + 14 + return 14 + frame_table_size(self.server_properties) + len(self.mechanisms) + len(self.locales) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= ConnectionStart.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset server_properties, delta = deframe_table(buf, offset) offset += delta version_major, version_minor, s_len, = struct.unpack_from('!BBL', buf, offset) @@ -486,31 +401,20 @@ class ConnectionSecure(AMQPMethodPayload): received sufficient information to authenticate each other. This method challenges the client to provide more information. """ - CLASS = Connection - NAME = u'secure' - CLASSNAME = u'connection' - FULLNAME = u'connection.secure' - - CONTENT_PROPERTY_LIST = ConnectionContentPropertyList + NAME = u'connection.secure' - CLASS_INDEX = 10 - CLASS_INDEX_BINARY = b'\x0A' - METHOD_INDEX = 20 - METHOD_INDEX_BINARY = b'\x14' + INDEX = (10, 20) # (Class ID, Method ID) BINARY_HEADER = b'\x0A\x14' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [ConnectionSecureOk] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 19 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'challenge', u'longstr', u'longstr', False), # security challenge data + + # See constructor pydoc for details + FIELDS = [ + Field(u'challenge', u'longstr', reserved=False), ] def __init__(self, challenge): @@ -525,16 +429,15 @@ class ConnectionSecure(AMQPMethodPayload): self.challenge = challenge def write_arguments(self, buf): - buf.write(struct.pack('!L', len(self.challenge))) + buf.write(struct.pack('!I', len(self.challenge))) buf.write(self.challenge) - + def get_size(self): - return len(self.challenge) + 4 + return 4 + len(self.challenge) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= ConnectionSecure.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset s_len, = struct.unpack_from('!L', buf, offset) offset += 4 challenge = buf[offset:offset+s_len] @@ -548,35 +451,24 @@ class ConnectionStartOk(AMQPMethodPayload): This method selects a SASL security mechanism. """ - CLASS = Connection - NAME = u'start-ok' - CLASSNAME = u'connection' - FULLNAME = u'connection.start-ok' - - CONTENT_PROPERTY_LIST = ConnectionContentPropertyList + NAME = u'connection.start-ok' - CLASS_INDEX = 10 - CLASS_INDEX_BINARY = b'\x0A' - METHOD_INDEX = 11 - METHOD_INDEX_BINARY = b'\x0B' + INDEX = (10, 11) # (Class ID, Method ID) BINARY_HEADER = b'\x0A\x0B' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 52 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'client-properties', u'peer-properties', u'table', False), # client properties - (u'mechanism', u'shortstr', u'shortstr', False), # selected security mechanism - (u'response', u'longstr', u'longstr', False), # security response data - (u'locale', u'shortstr', u'shortstr', False), # selected message locale + + # 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), ] def __init__(self, client_properties, mechanism, response, locale): @@ -588,7 +480,7 @@ class ConnectionStartOk(AMQPMethodPayload): of the client product, "version", giving the name of the client version, "platform", giving the name of the operating system, "copyright", if appropriate, and "information", giving other general information. - :type client_properties: table. See coolamqp.uplink.frames.field_table (peer-properties in AMQP) + :type client_properties: table. See coolamqp.uplink.framing.field_table (peer-properties in AMQP) :param mechanism: Selected security mechanism A single security mechanisms selected by the client, which must be one of those specified by the server. @@ -611,18 +503,17 @@ class ConnectionStartOk(AMQPMethodPayload): enframe_table(buf, self.client_properties) buf.write(struct.pack('!B', len(self.mechanism))) buf.write(self.mechanism) - buf.write(struct.pack('!L', len(self.response))) + buf.write(struct.pack('!I', len(self.response))) buf.write(self.response) buf.write(struct.pack('!B', len(self.locale))) buf.write(self.locale) - + def get_size(self): - return frame_table_size(self.client_properties) + len(self.mechanism) + len(self.response) + len(self.locale) + 10 + return 10 + frame_table_size(self.client_properties) + len(self.mechanism) + len(self.response) + len(self.locale) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= ConnectionStartOk.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset client_properties, delta = deframe_table(buf, offset) offset += delta s_len, = struct.unpack_from('!B', buf, offset) @@ -647,32 +538,21 @@ class ConnectionSecureOk(AMQPMethodPayload): This method attempts to authenticate, passing a block of SASL data for the security mechanism at the server side. """ - CLASS = Connection - NAME = u'secure-ok' - CLASSNAME = u'connection' - FULLNAME = u'connection.secure-ok' - - CONTENT_PROPERTY_LIST = ConnectionContentPropertyList + NAME = u'connection.secure-ok' - CLASS_INDEX = 10 - CLASS_INDEX_BINARY = b'\x0A' - METHOD_INDEX = 21 - METHOD_INDEX_BINARY = b'\x15' + INDEX = (10, 21) # (Class ID, Method ID) BINARY_HEADER = b'\x0A\x15' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 19 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'response', u'longstr', u'longstr', False), # security response data + + # See constructor pydoc for details + FIELDS = [ + Field(u'response', u'longstr', reserved=False), ] def __init__(self, response): @@ -687,16 +567,15 @@ class ConnectionSecureOk(AMQPMethodPayload): self.response = response def write_arguments(self, buf): - buf.write(struct.pack('!L', len(self.response))) + buf.write(struct.pack('!I', len(self.response))) buf.write(self.response) - + def get_size(self): - return len(self.response) + 4 + return 4 + len(self.response) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= ConnectionSecureOk.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset s_len, = struct.unpack_from('!L', buf, offset) offset += 4 response = buf[offset:offset+s_len] @@ -711,33 +590,22 @@ class ConnectionTune(AMQPMethodPayload): This method proposes a set of connection configuration values to the client. The client can accept and/or adjust these. """ - CLASS = Connection - NAME = u'tune' - CLASSNAME = u'connection' - FULLNAME = u'connection.tune' - - CONTENT_PROPERTY_LIST = ConnectionContentPropertyList + NAME = u'connection.tune' - CLASS_INDEX = 10 - CLASS_INDEX_BINARY = b'\x0A' - METHOD_INDEX = 30 - METHOD_INDEX_BINARY = b'\x1E' + INDEX = (10, 30) # (Class ID, Method ID) BINARY_HEADER = b'\x0A\x1E' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [ConnectionTuneOk] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 8 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'channel-max', u'short', u'short', False), # proposed maximum channels - (u'frame-max', u'long', u'long', False), # proposed maximum frame size - (u'heartbeat', u'short', u'short', False), # desired heartbeat delay + + # 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), ] def __init__(self, channel_max, frame_max, heartbeat): @@ -765,15 +633,15 @@ class ConnectionTune(AMQPMethodPayload): def write_arguments(self, buf): buf.write(struct.pack('!HIH', self.channel_max, self.frame_max, self.heartbeat)) - + def get_size(self): return 8 @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= ConnectionTune.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset channel_max, frame_max, heartbeat, = struct.unpack_from('!HIH', buf, offset) + offset += 8 return ConnectionTune(channel_max, frame_max, heartbeat) @@ -784,34 +652,23 @@ class ConnectionTuneOk(AMQPMethodPayload): This method sends the client's connection tuning parameters to the server. Certain fields are negotiated, others provide capability information. """ - CLASS = Connection - NAME = u'tune-ok' - CLASSNAME = u'connection' - FULLNAME = u'connection.tune-ok' + NAME = u'connection.tune-ok' - CONTENT_PROPERTY_LIST = ConnectionContentPropertyList - - CLASS_INDEX = 10 - CLASS_INDEX_BINARY = b'\x0A' - METHOD_INDEX = 31 - METHOD_INDEX_BINARY = b'\x1F' + INDEX = (10, 31) # (Class ID, Method ID) BINARY_HEADER = b'\x0A\x1F' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 8 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'channel-max', u'short', u'short', False), # negotiated maximum channels - (u'frame-max', u'long', u'long', False), # negotiated maximum frame size - (u'heartbeat', u'short', u'short', False), # desired heartbeat delay + + # 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), ] def __init__(self, channel_max, frame_max, heartbeat): @@ -839,15 +696,15 @@ class ConnectionTuneOk(AMQPMethodPayload): def write_arguments(self, buf): buf.write(struct.pack('!HIH', self.channel_max, self.frame_max, self.heartbeat)) - + def get_size(self): return 8 @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= ConnectionTuneOk.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset channel_max, frame_max, heartbeat, = struct.unpack_from('!HIH', buf, offset) + offset += 8 return ConnectionTuneOk(channel_max, frame_max, heartbeat) @@ -861,37 +718,6 @@ class Channel(AMQPClass): INDEX = 20 -class ChannelContentPropertyList(AMQPContentPropertyList): - """ - The channel class provides methods for a client to establish a channel to a - - server and for both peers to operate the channel thereafter. - """ - CLASS_NAME = u'channel' - CLASS_INDEX = 20 - CLASS = Channel - - CONTENT_PROPERTIES = [ # tuple of (name, domain, type) - - ] - - def __init__(self): - """ - Create the property list. - - """ - - def write_arguments(self, buf): - pass # this has a frame, but its only default shortstrs - - @staticmethod - def from_buffer(buf, start_offset): - return ChannelContentPropertyList() - - def get_size(self): - return 0 - - class ChannelClose(AMQPMethodPayload): """ Request a channel close @@ -901,34 +727,23 @@ class ChannelClose(AMQPMethodPayload): method, i.e. an exception. When a close is due to an exception, the sender provides the class and method id of the method which caused the exception. """ - CLASS = Channel - NAME = u'close' - CLASSNAME = u'channel' - FULLNAME = u'channel.close' - - CONTENT_PROPERTY_LIST = ChannelContentPropertyList + NAME = u'channel.close' - CLASS_INDEX = 20 - CLASS_INDEX_BINARY = b'\x14' - METHOD_INDEX = 40 - METHOD_INDEX_BINARY = b'\x28' + INDEX = (20, 40) # (Class ID, Method ID) BINARY_HEADER = b'\x14\x28' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [ChannelCloseOk] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = True - - MINIMUM_SIZE = 13 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'reply-code', u'reply-code', u'short', False), - (u'reply-text', u'reply-text', u'shortstr', False), - (u'class-id', u'class-id', u'short', False), # failing method class - (u'method-id', u'method-id', u'short', False), # failing method ID + + # 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), ] def __init__(self, reply_code, reply_text, class_id, method_id): @@ -954,14 +769,13 @@ class ChannelClose(AMQPMethodPayload): buf.write(struct.pack('!HB', self.reply_code, len(self.reply_text))) buf.write(self.reply_text) buf.write(struct.pack('!HH', self.class_id, self.method_id)) - + def get_size(self): - return len(self.reply_text) + 7 + return 7 + len(self.reply_text) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= ChannelClose.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset reply_code, s_len, = struct.unpack_from('!HB', buf, offset) offset += 3 reply_text = buf[offset:offset+s_len] @@ -978,26 +792,13 @@ class ChannelCloseOk(AMQPMethodPayload): This method confirms a Channel.Close method and tells the recipient that it is safe to release resources for the channel. """ - CLASS = Channel - NAME = u'close-ok' - CLASSNAME = u'channel' - FULLNAME = u'channel.close-ok' - - CONTENT_PROPERTY_LIST = ChannelContentPropertyList + NAME = u'channel.close-ok' - CLASS_INDEX = 20 - CLASS_INDEX_BINARY = b'\x14' - METHOD_INDEX = 41 - METHOD_INDEX_BINARY = b'\x29' + INDEX = (20, 41) # (Class ID, Method ID) BINARY_HEADER = b'\x14\x29' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = True - - MINIMUM_SIZE = 0 # arguments part can never be shorter than this + 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 @@ -1009,10 +810,10 @@ class ChannelCloseOk(AMQPMethodPayload): Create frame channel.close-ok """ - # not generating write_arguments - this method has static content! @staticmethod def from_buffer(buf, start_offset): + offset = start_offset return ChannelCloseOk() @@ -1026,31 +827,20 @@ class ChannelFlow(AMQPMethodPayload): it can process. Note that this method is not intended for window control. It does not affect contents returned by Basic.Get-Ok methods. """ - CLASS = Channel - NAME = u'flow' - CLASSNAME = u'channel' - FULLNAME = u'channel.flow' - - CONTENT_PROPERTY_LIST = ChannelContentPropertyList + NAME = u'channel.flow' - CLASS_INDEX = 20 - CLASS_INDEX_BINARY = b'\x14' - METHOD_INDEX = 20 - METHOD_INDEX_BINARY = b'\x14' + INDEX = (20, 20) # (Class ID, Method ID) BINARY_HEADER = b'\x14\x14' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [ChannelFlowOk] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = True - - MINIMUM_SIZE = 1 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'active', u'bit', u'bit', False), # start/stop content frames + + # See constructor pydoc for details + FIELDS = [ + Field(u'active', u'bit', reserved=False), ] def __init__(self, active): @@ -1065,17 +855,17 @@ class ChannelFlow(AMQPMethodPayload): self.active = active def write_arguments(self, buf): - buf.write(struct.pack('!B', (int(self.active) << 0))) - + buf.write(struct.pack('!B', (self.active << 0))) + def get_size(self): return 1 @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= ChannelFlow.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes - _bit_0, = struct.unpack_from('!B', buf, offset) - active = bool(_bit_0 & 1) + offset = start_offset + _bit, = struct.unpack_from('!B', buf, offset) + active = bool(_bit >> 0) + offset += 1 return ChannelFlow(active) @@ -1085,32 +875,21 @@ class ChannelFlowOk(AMQPMethodPayload): Confirms to the peer that a flow command was received and processed. """ - CLASS = Channel - NAME = u'flow-ok' - CLASSNAME = u'channel' - FULLNAME = u'channel.flow-ok' - - CONTENT_PROPERTY_LIST = ChannelContentPropertyList + NAME = u'channel.flow-ok' - CLASS_INDEX = 20 - CLASS_INDEX_BINARY = b'\x14' - METHOD_INDEX = 21 - METHOD_INDEX_BINARY = b'\x15' + INDEX = (20, 21) # (Class ID, Method ID) BINARY_HEADER = b'\x14\x15' # CLASS ID + METHOD ID - SYNCHRONOUS = False # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = True - - MINIMUM_SIZE = 1 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'active', u'bit', u'bit', False), # current flow setting + + # See constructor pydoc for details + FIELDS = [ + Field(u'active', u'bit', reserved=False), ] def __init__(self, active): @@ -1125,17 +904,17 @@ class ChannelFlowOk(AMQPMethodPayload): self.active = active def write_arguments(self, buf): - buf.write(struct.pack('!B', (int(self.active) << 0))) - + buf.write(struct.pack('!B', (self.active << 0))) + def get_size(self): return 1 @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= ChannelFlowOk.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes - _bit_0, = struct.unpack_from('!B', buf, offset) - active = bool(_bit_0 & 1) + offset = start_offset + _bit, = struct.unpack_from('!B', buf, offset) + active = bool(_bit >> 0) + offset += 1 return ChannelFlowOk(active) @@ -1145,32 +924,21 @@ class ChannelOpen(AMQPMethodPayload): This method opens a channel to the server. """ - CLASS = Channel - NAME = u'open' - CLASSNAME = u'channel' - FULLNAME = u'channel.open' - - CONTENT_PROPERTY_LIST = ChannelContentPropertyList + NAME = u'channel.open' - CLASS_INDEX = 20 - CLASS_INDEX_BINARY = b'\x14' - METHOD_INDEX = 10 - METHOD_INDEX_BINARY = b'\x0A' + INDEX = (20, 10) # (Class ID, Method ID) BINARY_HEADER = b'\x14\x0A' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [ChannelOpenOk] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 7 # arguments part can never be shorter than this + 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 STATIC_CONTENT = b'\x00\x00\x00\x05\x14\x0A\x00\xCE' # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'reserved-1', u'shortstr', u'shortstr', True), + + # See constructor pydoc for details + FIELDS = [ + Field(u'reserved-1', u'shortstr', reserved=True), ] def __init__(self): @@ -1178,10 +946,13 @@ class ChannelOpen(AMQPMethodPayload): Create frame channel.open """ - # not generating write_arguments - this method has static content! @staticmethod def from_buffer(buf, start_offset): + offset = start_offset + s_len, = struct.unpack_from('!B', buf, offset) + offset += 1 + offset += s_len # reserved field! return ChannelOpen() @@ -1191,33 +962,22 @@ class ChannelOpenOk(AMQPMethodPayload): This method signals to the client that the channel is ready for use. """ - CLASS = Channel - NAME = u'open-ok' - CLASSNAME = u'channel' - FULLNAME = u'channel.open-ok' + NAME = u'channel.open-ok' - CONTENT_PROPERTY_LIST = ChannelContentPropertyList - - CLASS_INDEX = 20 - CLASS_INDEX_BINARY = b'\x14' - METHOD_INDEX = 11 - METHOD_INDEX_BINARY = b'\x0B' + INDEX = (20, 11) # (Class ID, Method ID) BINARY_HEADER = b'\x14\x0B' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 19 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'reserved-1', u'longstr', u'longstr', True), + + # See constructor pydoc for details + FIELDS = [ + Field(u'reserved-1', u'longstr', reserved=True), ] def __init__(self): @@ -1225,10 +985,13 @@ class ChannelOpenOk(AMQPMethodPayload): Create frame channel.open-ok """ - # not generating write_arguments - this method has static content! @staticmethod def from_buffer(buf, start_offset): + offset = start_offset + s_len, = struct.unpack_from('!L', buf, offset) + offset += 4 + offset += s_len # reserved field! return ChannelOpenOk() @@ -1242,37 +1005,6 @@ class Exchange(AMQPClass): INDEX = 40 -class ExchangeContentPropertyList(AMQPContentPropertyList): - """ - Exchanges match and distribute messages across queues. exchanges can be configured in - - the server or declared at runtime. - """ - CLASS_NAME = u'exchange' - CLASS_INDEX = 40 - CLASS = Exchange - - CONTENT_PROPERTIES = [ # tuple of (name, domain, type) - - ] - - def __init__(self): - """ - Create the property list. - - """ - - def write_arguments(self, buf): - pass # this has a frame, but its only default shortstrs - - @staticmethod - def from_buffer(buf, start_offset): - return ExchangeContentPropertyList() - - def get_size(self): - return 0 - - class ExchangeDeclare(AMQPMethodPayload): """ Verify exchange exists, create if needed @@ -1280,42 +1012,31 @@ class ExchangeDeclare(AMQPMethodPayload): This method creates an exchange if it does not already exist, and if the exchange exists, verifies that it is of the correct and expected class. """ - CLASS = Exchange - NAME = u'declare' - CLASSNAME = u'exchange' - FULLNAME = u'exchange.declare' - - CONTENT_PROPERTY_LIST = ExchangeContentPropertyList + NAME = u'exchange.declare' - CLASS_INDEX = 40 - CLASS_INDEX_BINARY = b'\x28' - METHOD_INDEX = 10 - METHOD_INDEX_BINARY = b'\x0A' + INDEX = (40, 10) # (Class ID, Method ID) BINARY_HEADER = b'\x28\x0A' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [ExchangeDeclareOk] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 36 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'reserved-1', u'short', u'short', True), - (u'exchange', u'exchange-name', u'shortstr', False), - (u'type', u'shortstr', u'shortstr', False), # exchange type - (u'passive', u'bit', u'bit', False), # do not create exchange - (u'durable', u'bit', u'bit', False), # request a durable exchange - (u'reserved-2', u'bit', u'bit', True), - (u'reserved-3', u'bit', u'bit', True), - (u'no-wait', u'no-wait', u'bit', False), - (u'arguments', u'table', u'table', False), # arguments for declaration + + # 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), ] - def __init__(self, exchange, type, passive, durable, no_wait, arguments): + def __init__(self, exchange, type_, passive, durable, no_wait, arguments): """ Create frame exchange.declare @@ -1323,12 +1044,12 @@ class ExchangeDeclare(AMQPMethodPayload): standardised exchanges. The client MAY declare an exchange starting with "amq." if the passive option is set, or the exchange already exists. :type exchange: binary type (max length 255) (exchange-name in AMQP) - :param type: Exchange type + :param type_: Exchange type Each exchange belongs to one of a set of exchange types implemented by the server. The exchange types define the functionality of the exchange - i.e. how messages are routed through it. It is not valid or meaningful to attempt to change the type of an existing exchange. - :type type: binary type (max length 255) (shortstr in AMQP) + :type type_: binary type (max length 255) (shortstr in AMQP) :param passive: Do not create exchange If set, the server will reply with Declare-Ok if the exchange already exists with the same name, and raise an error if not. The client can @@ -1346,47 +1067,45 @@ class ExchangeDeclare(AMQPMethodPayload): :param arguments: Arguments for declaration A set of arguments for the declaration. The syntax and semantics of these arguments depends on the server implementation. - :type arguments: table. See coolamqp.uplink.frames.field_table (table in AMQP) + :type arguments: table. See coolamqp.uplink.framing.field_table (table in AMQP) """ self.exchange = exchange - self.type = type + self.type_ = type_ self.passive = passive self.durable = durable self.no_wait = no_wait self.arguments = arguments def write_arguments(self, buf): - buf.write(b'\x00\x00') - buf.write(struct.pack('!B', len(self.exchange))) + buf.write(struct.pack('!HB', self.reserved_1, len(self.exchange))) buf.write(self.exchange) - buf.write(struct.pack('!B', len(self.type))) - buf.write(self.type) - buf.write(struct.pack('!B', (int(self.passive) << 0) | (int(self.durable) << 1) | (int(self.no_wait) << 2))) + buf.write(struct.pack('!B', len(self.type_))) + buf.write(self.type_) enframe_table(buf, self.arguments) - + buf.write(struct.pack('!B', (self.passive << 0) | (self.durable << 1) | (self.no_wait << 4))) + def get_size(self): - return len(self.exchange) + len(self.type) + frame_table_size(self.arguments) + 9 + return 9 + len(self.exchange) + len(self.type_) + frame_table_size(self.arguments) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= ExchangeDeclare.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset s_len, = struct.unpack_from('!2xB', buf, offset) offset += 3 exchange = buf[offset:offset+s_len] offset += s_len s_len, = struct.unpack_from('!B', buf, offset) offset += 1 - type = buf[offset:offset+s_len] + type_ = buf[offset:offset+s_len] offset += s_len _bit, = struct.unpack_from('!B', buf, offset) + passive = bool(_bit >> 0) + durable = bool(_bit >> 1) + no_wait = bool(_bit >> 4) offset += 1 - passive = bool(_bit & 1) - durable = bool(_bit & 2) - no_wait = bool(_bit & 16) arguments, delta = deframe_table(buf, offset) offset += delta - return ExchangeDeclare(exchange, type, passive, durable, no_wait, arguments) + return ExchangeDeclare(exchange, type_, passive, durable, no_wait, arguments) class ExchangeDelete(AMQPMethodPayload): @@ -1396,34 +1115,23 @@ class ExchangeDelete(AMQPMethodPayload): This method deletes an exchange. When an exchange is deleted all queue bindings on the exchange are cancelled. """ - CLASS = Exchange - NAME = u'delete' - CLASSNAME = u'exchange' - FULLNAME = u'exchange.delete' - - CONTENT_PROPERTY_LIST = ExchangeContentPropertyList + NAME = u'exchange.delete' - CLASS_INDEX = 40 - CLASS_INDEX_BINARY = b'\x28' - METHOD_INDEX = 20 - METHOD_INDEX_BINARY = b'\x14' + INDEX = (40, 20) # (Class ID, Method ID) BINARY_HEADER = b'\x28\x14' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [ExchangeDeleteOk] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 10 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'reserved-1', u'short', u'short', True), - (u'exchange', u'exchange-name', u'shortstr', False), - (u'if-unused', u'bit', u'bit', False), # delete only if unused - (u'no-wait', u'no-wait', u'bit', False), + + # 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), ] def __init__(self, exchange, if_unused, no_wait): @@ -1444,26 +1152,24 @@ class ExchangeDelete(AMQPMethodPayload): self.no_wait = no_wait def write_arguments(self, buf): - buf.write(b'\x00\x00') - buf.write(struct.pack('!B', len(self.exchange))) + buf.write(struct.pack('!HB', self.reserved_1, len(self.exchange))) buf.write(self.exchange) - buf.write(struct.pack('!B', (int(self.if_unused) << 0) | (int(self.no_wait) << 1))) - + buf.write(struct.pack('!B', (self.if_unused << 0) | (self.no_wait << 1))) + def get_size(self): - return len(self.exchange) + 4 + return 4 + len(self.exchange) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= ExchangeDelete.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset s_len, = struct.unpack_from('!2xB', buf, offset) offset += 3 exchange = buf[offset:offset+s_len] offset += s_len _bit, = struct.unpack_from('!B', buf, offset) + if_unused = bool(_bit >> 0) + no_wait = bool(_bit >> 1) offset += 1 - if_unused = bool(_bit & 1) - no_wait = bool(_bit & 2) return ExchangeDelete(exchange, if_unused, no_wait) @@ -1474,26 +1180,13 @@ class ExchangeDeclareOk(AMQPMethodPayload): This method confirms a Declare method and confirms the name of the exchange, essential for automatically-named exchanges. """ - CLASS = Exchange - NAME = u'declare-ok' - CLASSNAME = u'exchange' - FULLNAME = u'exchange.declare-ok' + NAME = u'exchange.declare-ok' - CONTENT_PROPERTY_LIST = ExchangeContentPropertyList - - CLASS_INDEX = 40 - CLASS_INDEX_BINARY = b'\x28' - METHOD_INDEX = 11 - METHOD_INDEX_BINARY = b'\x0B' + INDEX = (40, 11) # (Class ID, Method ID) BINARY_HEADER = b'\x28\x0B' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 0 # arguments part can never be shorter than this + 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 @@ -1505,10 +1198,10 @@ class ExchangeDeclareOk(AMQPMethodPayload): Create frame exchange.declare-ok """ - # not generating write_arguments - this method has static content! @staticmethod def from_buffer(buf, start_offset): + offset = start_offset return ExchangeDeclareOk() @@ -1518,26 +1211,13 @@ class ExchangeDeleteOk(AMQPMethodPayload): This method confirms the deletion of an exchange. """ - CLASS = Exchange - NAME = u'delete-ok' - CLASSNAME = u'exchange' - FULLNAME = u'exchange.delete-ok' - - CONTENT_PROPERTY_LIST = ExchangeContentPropertyList + NAME = u'exchange.delete-ok' - CLASS_INDEX = 40 - CLASS_INDEX_BINARY = b'\x28' - METHOD_INDEX = 21 - METHOD_INDEX_BINARY = b'\x15' + INDEX = (40, 21) # (Class ID, Method ID) BINARY_HEADER = b'\x28\x15' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 0 # arguments part can never be shorter than this + 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 @@ -1549,10 +1229,10 @@ class ExchangeDeleteOk(AMQPMethodPayload): Create frame exchange.delete-ok """ - # not generating write_arguments - this method has static content! @staticmethod def from_buffer(buf, start_offset): + offset = start_offset return ExchangeDeleteOk() @@ -1567,38 +1247,6 @@ class Queue(AMQPClass): INDEX = 50 -class QueueContentPropertyList(AMQPContentPropertyList): - """ - Queues store and forward messages. queues can be configured in the server or created at - - runtime. Queues must be attached to at least one exchange in order to receive messages - from publishers. - """ - CLASS_NAME = u'queue' - CLASS_INDEX = 50 - CLASS = Queue - - CONTENT_PROPERTIES = [ # tuple of (name, domain, type) - - ] - - def __init__(self): - """ - Create the property list. - - """ - - def write_arguments(self, buf): - pass # this has a frame, but its only default shortstrs - - @staticmethod - def from_buffer(buf, start_offset): - return QueueContentPropertyList() - - def get_size(self): - return 0 - - class QueueBind(AMQPMethodPayload): """ Bind queue to an exchange @@ -1608,36 +1256,25 @@ class QueueBind(AMQPMethodPayload): are bound to a direct exchange and subscription queues are bound to a topic exchange. """ - CLASS = Queue - NAME = u'bind' - CLASSNAME = u'queue' - FULLNAME = u'queue.bind' + NAME = u'queue.bind' - CONTENT_PROPERTY_LIST = QueueContentPropertyList - - CLASS_INDEX = 50 - CLASS_INDEX_BINARY = b'\x32' - METHOD_INDEX = 20 - METHOD_INDEX_BINARY = b'\x14' + INDEX = (50, 20) # (Class ID, Method ID) BINARY_HEADER = b'\x32\x14' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [QueueBindOk] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 43 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'reserved-1', u'short', u'short', True), - (u'queue', u'queue-name', u'shortstr', False), - (u'exchange', u'exchange-name', u'shortstr', False), # name of the exchange to bind to - (u'routing-key', u'shortstr', u'shortstr', False), # message routing key - (u'no-wait', u'no-wait', u'bit', False), - (u'arguments', u'table', u'table', False), # arguments for binding + + # 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), ] def __init__(self, queue, exchange, routing_key, no_wait, arguments): @@ -1663,7 +1300,7 @@ class QueueBind(AMQPMethodPayload): :param arguments: Arguments for binding A set of arguments for the binding. The syntax and semantics of these arguments depends on the exchange class. - :type arguments: table. See coolamqp.uplink.frames.field_table (table in AMQP) + :type arguments: table. See coolamqp.uplink.framing.field_table (table in AMQP) """ self.queue = queue self.exchange = exchange @@ -1672,23 +1309,21 @@ class QueueBind(AMQPMethodPayload): self.arguments = arguments def write_arguments(self, buf): - buf.write(b'\x00\x00') - buf.write(struct.pack('!B', len(self.queue))) + buf.write(struct.pack('!HB', self.reserved_1, len(self.queue))) buf.write(self.queue) buf.write(struct.pack('!B', len(self.exchange))) buf.write(self.exchange) buf.write(struct.pack('!B', len(self.routing_key))) buf.write(self.routing_key) - buf.write(struct.pack('!B', (int(self.no_wait) << 0))) enframe_table(buf, self.arguments) - + buf.write(struct.pack('!B', (self.no_wait << 0))) + def get_size(self): - return len(self.queue) + len(self.exchange) + len(self.routing_key) + frame_table_size(self.arguments) + 10 + return 10 + len(self.queue) + len(self.exchange) + len(self.routing_key) + frame_table_size(self.arguments) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= QueueBind.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset s_len, = struct.unpack_from('!2xB', buf, offset) offset += 3 queue = buf[offset:offset+s_len] @@ -1702,8 +1337,8 @@ class QueueBind(AMQPMethodPayload): routing_key = buf[offset:offset+s_len] offset += s_len _bit, = struct.unpack_from('!B', buf, offset) + no_wait = bool(_bit >> 0) offset += 1 - no_wait = bool(_bit & 1) arguments, delta = deframe_table(buf, offset) offset += delta return QueueBind(queue, exchange, routing_key, no_wait, arguments) @@ -1715,26 +1350,13 @@ class QueueBindOk(AMQPMethodPayload): This method confirms that the bind was successful. """ - CLASS = Queue - NAME = u'bind-ok' - CLASSNAME = u'queue' - FULLNAME = u'queue.bind-ok' - - CONTENT_PROPERTY_LIST = QueueContentPropertyList + NAME = u'queue.bind-ok' - CLASS_INDEX = 50 - CLASS_INDEX_BINARY = b'\x32' - METHOD_INDEX = 21 - METHOD_INDEX_BINARY = b'\x15' + INDEX = (50, 21) # (Class ID, Method ID) BINARY_HEADER = b'\x32\x15' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 0 # arguments part can never be shorter than this + 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 @@ -1746,10 +1368,10 @@ class QueueBindOk(AMQPMethodPayload): Create frame queue.bind-ok """ - # not generating write_arguments - this method has static content! @staticmethod def from_buffer(buf, start_offset): + offset = start_offset return QueueBindOk() @@ -1761,38 +1383,27 @@ class QueueDeclare(AMQPMethodPayload): specify various properties that control the durability of the queue and its contents, and the level of sharing for the queue. """ - CLASS = Queue - NAME = u'declare' - CLASSNAME = u'queue' - FULLNAME = u'queue.declare' - - CONTENT_PROPERTY_LIST = QueueContentPropertyList + NAME = u'queue.declare' - CLASS_INDEX = 50 - CLASS_INDEX_BINARY = b'\x32' - METHOD_INDEX = 10 - METHOD_INDEX_BINARY = b'\x0A' + INDEX = (50, 10) # (Class ID, Method ID) BINARY_HEADER = b'\x32\x0A' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [QueueDeclareOk] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 29 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'reserved-1', u'short', u'short', True), - (u'queue', u'queue-name', u'shortstr', False), - (u'passive', u'bit', u'bit', False), # do not create queue - (u'durable', u'bit', u'bit', False), # request a durable queue - (u'exclusive', u'bit', u'bit', False), # request an exclusive queue - (u'auto-delete', u'bit', u'bit', False), # auto-delete queue when unused - (u'no-wait', u'no-wait', u'bit', False), - (u'arguments', u'table', u'table', False), # arguments for declaration + + # 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), ] def __init__(self, queue, passive, durable, exclusive, auto_delete, no_wait, arguments): @@ -1833,7 +1444,7 @@ class QueueDeclare(AMQPMethodPayload): :param arguments: Arguments for declaration A set of arguments for the declaration. The syntax and semantics of these arguments depends on the server implementation. - :type arguments: table. See coolamqp.uplink.frames.field_table (table in AMQP) + :type arguments: table. See coolamqp.uplink.framing.field_table (table in AMQP) """ self.queue = queue self.passive = passive @@ -1844,30 +1455,28 @@ class QueueDeclare(AMQPMethodPayload): self.arguments = arguments def write_arguments(self, buf): - buf.write(b'\x00\x00') - buf.write(struct.pack('!B', len(self.queue))) + buf.write(struct.pack('!HB', self.reserved_1, len(self.queue))) buf.write(self.queue) - buf.write(struct.pack('!B', (int(self.passive) << 0) | (int(self.durable) << 1) | (int(self.exclusive) << 2) | (int(self.auto_delete) << 3) | (int(self.no_wait) << 4))) enframe_table(buf, self.arguments) - + buf.write(struct.pack('!B', (self.passive << 0) | (self.durable << 1) | (self.exclusive << 2) | (self.auto_delete << 3) | (self.no_wait << 4))) + def get_size(self): - return len(self.queue) + frame_table_size(self.arguments) + 8 + return 8 + len(self.queue) + frame_table_size(self.arguments) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= QueueDeclare.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset s_len, = struct.unpack_from('!2xB', buf, offset) offset += 3 queue = buf[offset:offset+s_len] offset += s_len _bit, = struct.unpack_from('!B', buf, offset) + passive = bool(_bit >> 0) + durable = bool(_bit >> 1) + exclusive = bool(_bit >> 2) + auto_delete = bool(_bit >> 3) + no_wait = bool(_bit >> 4) offset += 1 - passive = bool(_bit & 1) - durable = bool(_bit & 2) - exclusive = bool(_bit & 4) - auto_delete = bool(_bit & 8) - no_wait = bool(_bit & 16) arguments, delta = deframe_table(buf, offset) offset += delta return QueueDeclare(queue, passive, durable, exclusive, auto_delete, no_wait, arguments) @@ -1881,35 +1490,24 @@ class QueueDelete(AMQPMethodPayload): to a dead-letter queue if this is defined in the server configuration, and all consumers on the queue are cancelled. """ - CLASS = Queue - NAME = u'delete' - CLASSNAME = u'queue' - FULLNAME = u'queue.delete' - - CONTENT_PROPERTY_LIST = QueueContentPropertyList + NAME = u'queue.delete' - CLASS_INDEX = 50 - CLASS_INDEX_BINARY = b'\x32' - METHOD_INDEX = 40 - METHOD_INDEX_BINARY = b'\x28' + INDEX = (50, 40) # (Class ID, Method ID) BINARY_HEADER = b'\x32\x28' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [QueueDeleteOk] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 10 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'reserved-1', u'short', u'short', True), - (u'queue', u'queue-name', u'shortstr', False), - (u'if-unused', u'bit', u'bit', False), # delete only if unused - (u'if-empty', u'bit', u'bit', False), # delete only if empty - (u'no-wait', u'no-wait', u'bit', False), + + # 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), ] def __init__(self, queue, if_unused, if_empty, no_wait): @@ -1934,27 +1532,25 @@ class QueueDelete(AMQPMethodPayload): self.no_wait = no_wait def write_arguments(self, buf): - buf.write(b'\x00\x00') - buf.write(struct.pack('!B', len(self.queue))) + buf.write(struct.pack('!HB', self.reserved_1, len(self.queue))) buf.write(self.queue) - buf.write(struct.pack('!B', (int(self.if_unused) << 0) | (int(self.if_empty) << 1) | (int(self.no_wait) << 2))) - + buf.write(struct.pack('!B', (self.if_unused << 0) | (self.if_empty << 1) | (self.no_wait << 2))) + def get_size(self): - return len(self.queue) + 4 + return 4 + len(self.queue) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= QueueDelete.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset s_len, = struct.unpack_from('!2xB', buf, offset) offset += 3 queue = buf[offset:offset+s_len] offset += s_len _bit, = struct.unpack_from('!B', buf, offset) + if_unused = bool(_bit >> 0) + if_empty = bool(_bit >> 1) + no_wait = bool(_bit >> 2) offset += 1 - if_unused = bool(_bit & 1) - if_empty = bool(_bit & 2) - no_wait = bool(_bit & 4) return QueueDelete(queue, if_unused, if_empty, no_wait) @@ -1965,34 +1561,23 @@ class QueueDeclareOk(AMQPMethodPayload): This method confirms a Declare method and confirms the name of the queue, essential for automatically-named queues. """ - CLASS = Queue - NAME = u'declare-ok' - CLASSNAME = u'queue' - FULLNAME = u'queue.declare-ok' - - CONTENT_PROPERTY_LIST = QueueContentPropertyList + NAME = u'queue.declare-ok' - CLASS_INDEX = 50 - CLASS_INDEX_BINARY = b'\x32' - METHOD_INDEX = 11 - METHOD_INDEX_BINARY = b'\x0B' + INDEX = (50, 11) # (Class ID, Method ID) BINARY_HEADER = b'\x32\x0B' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 15 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'queue', u'queue-name', u'shortstr', False), - (u'message-count', u'message-count', u'long', False), - (u'consumer-count', u'long', u'long', False), # number of consumers + + # 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), ] def __init__(self, queue, message_count, consumer_count): @@ -2016,14 +1601,13 @@ class QueueDeclareOk(AMQPMethodPayload): buf.write(struct.pack('!B', len(self.queue))) buf.write(self.queue) buf.write(struct.pack('!II', self.message_count, self.consumer_count)) - + def get_size(self): - return len(self.queue) + 9 + return 9 + len(self.queue) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= QueueDeclareOk.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset s_len, = struct.unpack_from('!B', buf, offset) offset += 1 queue = buf[offset:offset+s_len] @@ -2039,32 +1623,21 @@ class QueueDeleteOk(AMQPMethodPayload): This method confirms the deletion of a queue. """ - CLASS = Queue - NAME = u'delete-ok' - CLASSNAME = u'queue' - FULLNAME = u'queue.delete-ok' - - CONTENT_PROPERTY_LIST = QueueContentPropertyList + NAME = u'queue.delete-ok' - CLASS_INDEX = 50 - CLASS_INDEX_BINARY = b'\x32' - METHOD_INDEX = 41 - METHOD_INDEX_BINARY = b'\x29' + INDEX = (50, 41) # (Class ID, Method ID) BINARY_HEADER = b'\x32\x29' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 4 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'message-count', u'message-count', u'long', False), + + # See constructor pydoc for details + FIELDS = [ + Field(u'message-count', u'long', reserved=False), ] def __init__(self, message_count): @@ -2078,15 +1651,15 @@ class QueueDeleteOk(AMQPMethodPayload): def write_arguments(self, buf): buf.write(struct.pack('!I', self.message_count)) - + def get_size(self): return 4 @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= QueueDeleteOk.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset message_count, = struct.unpack_from('!I', buf, offset) + offset += 4 return QueueDeleteOk(message_count) @@ -2097,33 +1670,22 @@ class QueuePurge(AMQPMethodPayload): This method removes all messages from a queue which are not awaiting acknowledgment. """ - CLASS = Queue - NAME = u'purge' - CLASSNAME = u'queue' - FULLNAME = u'queue.purge' + NAME = u'queue.purge' - CONTENT_PROPERTY_LIST = QueueContentPropertyList - - CLASS_INDEX = 50 - CLASS_INDEX_BINARY = b'\x32' - METHOD_INDEX = 30 - METHOD_INDEX_BINARY = b'\x1E' + INDEX = (50, 30) # (Class ID, Method ID) BINARY_HEADER = b'\x32\x1E' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [QueuePurgeOk] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 10 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'reserved-1', u'short', u'short', True), - (u'queue', u'queue-name', u'shortstr', False), - (u'no-wait', u'no-wait', u'bit', False), + + # 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), ] def __init__(self, queue, no_wait): @@ -2138,25 +1700,23 @@ class QueuePurge(AMQPMethodPayload): self.no_wait = no_wait def write_arguments(self, buf): - buf.write(b'\x00\x00') - buf.write(struct.pack('!B', len(self.queue))) + buf.write(struct.pack('!HB', self.reserved_1, len(self.queue))) buf.write(self.queue) - buf.write(struct.pack('!B', (int(self.no_wait) << 0))) - + buf.write(struct.pack('!B', (self.no_wait << 0))) + def get_size(self): - return len(self.queue) + 4 + return 4 + len(self.queue) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= QueuePurge.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset s_len, = struct.unpack_from('!2xB', buf, offset) offset += 3 queue = buf[offset:offset+s_len] offset += s_len _bit, = struct.unpack_from('!B', buf, offset) + no_wait = bool(_bit >> 0) offset += 1 - no_wait = bool(_bit & 1) return QueuePurge(queue, no_wait) @@ -2166,32 +1726,21 @@ class QueuePurgeOk(AMQPMethodPayload): This method confirms the purge of a queue. """ - CLASS = Queue - NAME = u'purge-ok' - CLASSNAME = u'queue' - FULLNAME = u'queue.purge-ok' - - CONTENT_PROPERTY_LIST = QueueContentPropertyList + NAME = u'queue.purge-ok' - CLASS_INDEX = 50 - CLASS_INDEX_BINARY = b'\x32' - METHOD_INDEX = 31 - METHOD_INDEX_BINARY = b'\x1F' + INDEX = (50, 31) # (Class ID, Method ID) BINARY_HEADER = b'\x32\x1F' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 4 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'message-count', u'message-count', u'long', False), + + # See constructor pydoc for details + FIELDS = [ + Field(u'message-count', u'long', reserved=False), ] def __init__(self, message_count): @@ -2205,15 +1754,15 @@ class QueuePurgeOk(AMQPMethodPayload): def write_arguments(self, buf): buf.write(struct.pack('!I', self.message_count)) - + def get_size(self): return 4 @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= QueuePurgeOk.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset message_count, = struct.unpack_from('!I', buf, offset) + offset += 4 return QueuePurgeOk(message_count) @@ -2223,35 +1772,24 @@ class QueueUnbind(AMQPMethodPayload): This method unbinds a queue from an exchange. """ - CLASS = Queue - NAME = u'unbind' - CLASSNAME = u'queue' - FULLNAME = u'queue.unbind' + NAME = u'queue.unbind' - CONTENT_PROPERTY_LIST = QueueContentPropertyList - - CLASS_INDEX = 50 - CLASS_INDEX_BINARY = b'\x32' - METHOD_INDEX = 50 - METHOD_INDEX_BINARY = b'\x32' + INDEX = (50, 50) # (Class ID, Method ID) BINARY_HEADER = b'\x32\x32' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [QueueUnbindOk] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 42 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'reserved-1', u'short', u'short', True), - (u'queue', u'queue-name', u'shortstr', False), - (u'exchange', u'exchange-name', u'shortstr', False), - (u'routing-key', u'shortstr', u'shortstr', False), # routing key of binding - (u'arguments', u'table', u'table', False), # arguments of binding + + # 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), ] def __init__(self, queue, exchange, routing_key, arguments): @@ -2267,7 +1805,7 @@ class QueueUnbind(AMQPMethodPayload): :type routing_key: binary type (max length 255) (shortstr in AMQP) :param arguments: Arguments of binding Specifies the arguments of the binding to unbind. - :type arguments: table. See coolamqp.uplink.frames.field_table (table in AMQP) + :type arguments: table. See coolamqp.uplink.framing.field_table (table in AMQP) """ self.queue = queue self.exchange = exchange @@ -2275,22 +1813,20 @@ class QueueUnbind(AMQPMethodPayload): self.arguments = arguments def write_arguments(self, buf): - buf.write(b'\x00\x00') - buf.write(struct.pack('!B', len(self.queue))) + buf.write(struct.pack('!HB', self.reserved_1, len(self.queue))) buf.write(self.queue) buf.write(struct.pack('!B', len(self.exchange))) buf.write(self.exchange) buf.write(struct.pack('!B', len(self.routing_key))) buf.write(self.routing_key) enframe_table(buf, self.arguments) - + def get_size(self): - return len(self.queue) + len(self.exchange) + len(self.routing_key) + frame_table_size(self.arguments) + 9 + return 9 + len(self.queue) + len(self.exchange) + len(self.routing_key) + frame_table_size(self.arguments) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= QueueUnbind.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset s_len, = struct.unpack_from('!2xB', buf, offset) offset += 3 queue = buf[offset:offset+s_len] @@ -2314,26 +1850,13 @@ class QueueUnbindOk(AMQPMethodPayload): This method confirms that the unbind was successful. """ - CLASS = Queue - NAME = u'unbind-ok' - CLASSNAME = u'queue' - FULLNAME = u'queue.unbind-ok' - - CONTENT_PROPERTY_LIST = QueueContentPropertyList + NAME = u'queue.unbind-ok' - CLASS_INDEX = 50 - CLASS_INDEX_BINARY = b'\x32' - METHOD_INDEX = 51 - METHOD_INDEX_BINARY = b'\x33' + INDEX = (50, 51) # (Class ID, Method ID) BINARY_HEADER = b'\x32\x33' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 0 # arguments part can never be shorter than this + 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 @@ -2345,10 +1868,10 @@ class QueueUnbindOk(AMQPMethodPayload): Create frame queue.unbind-ok """ - # not generating write_arguments - this method has static content! @staticmethod def from_buffer(buf, start_offset): + offset = start_offset return QueueUnbindOk() @@ -2364,145 +1887,23 @@ class BasicContentPropertyList(AMQPContentPropertyList): """ The basic class provides methods that support an industry-standard messaging model. """ - CLASS_NAME = u'basic' - CLASS_INDEX = 60 - CLASS = Basic - - CONTENT_PROPERTIES = [ # tuple of (name, domain, type) - - (u'content-type', u'shortstr', u'shortstr'), # u'MIME content type' - (u'content-encoding', u'shortstr', u'shortstr'), # u'MIME content encoding' - (u'headers', u'table', u'table'), # u'message header field table' - (u'delivery-mode', u'octet', u'octet'), # u'non-persistent (1) or persistent (2)' - (u'priority', u'octet', u'octet'), # u'message priority, 0 to 9' - (u'correlation-id', u'shortstr', u'shortstr'), # u'application correlation identifier' - (u'reply-to', u'shortstr', u'shortstr'), # u'address to reply to' - (u'expiration', u'shortstr', u'shortstr'), # u'message expiration specification' - (u'message-id', u'shortstr', u'shortstr'), # u'application message identifier' - (u'timestamp', u'timestamp', u'timestamp'), # u'message timestamp' - (u'type', u'shortstr', u'shortstr'), # u'message type name' - (u'user-id', u'shortstr', u'shortstr'), # u'creating user id' - (u'app-id', u'shortstr', u'shortstr'), # u'creating application id' - (u'reserved', u'shortstr', u'shortstr'), # u'reserved, must be empty' + 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), ] - def __init__(self, content_type, content_encoding, headers, delivery_mode, priority, correlation_id, reply_to, expiration, message_id, timestamp, type, user_id, app_id): - """ - Create the property list. - - :param content_type: MIME content type - :type content_type: binary type (max length 255) (shortstr in AMQP) - :param content_encoding: MIME content encoding - :type content_encoding: binary type (max length 255) (shortstr in AMQP) - :param headers: message header field table - :type headers: table. See coolamqp.uplink.frames.field_table (table in AMQP) - :param delivery_mode: non-persistent (1) or persistent (2) - :type delivery_mode: int, 8 bit unsigned (octet in AMQP) - :param priority: message priority, 0 to 9 - :type priority: int, 8 bit unsigned (octet in AMQP) - :param correlation_id: application correlation identifier - :type correlation_id: binary type (max length 255) (shortstr in AMQP) - :param reply_to: address to reply to - :type reply_to: binary type (max length 255) (shortstr in AMQP) - :param expiration: message expiration specification - :type expiration: binary type (max length 255) (shortstr in AMQP) - :param message_id: application message identifier - :type message_id: binary type (max length 255) (shortstr in AMQP) - :param timestamp: message timestamp - :type timestamp: 64 bit signed POSIX timestamp (in seconds) (timestamp in AMQP) - :param type: message type name - :type type: binary type (max length 255) (shortstr in AMQP) - :param user_id: creating user id - :type user_id: binary type (max length 255) (shortstr in AMQP) - :param app_id: creating application id - :type app_id: binary type (max length 255) (shortstr in AMQP) - """ - self.content_type = content_type # MIME content type - self.content_encoding = content_encoding # MIME content encoding - self.headers = headers # message header field table - self.delivery_mode = delivery_mode # non-persistent (1) or persistent (2) - self.priority = priority # message priority, 0 to 9 - self.correlation_id = correlation_id # application correlation identifier - self.reply_to = reply_to # address to reply to - self.expiration = expiration # message expiration specification - self.message_id = message_id # application message identifier - self.timestamp = timestamp # message timestamp - self.type = type # message type name - self.user_id = user_id # creating user id - self.app_id = app_id # creating application id - - def write_arguments(self, buf): - buf.write(struct.pack('!B', len(self.content_type))) - buf.write(self.content_type) - buf.write(struct.pack('!B', len(self.content_encoding))) - buf.write(self.content_encoding) - enframe_table(buf, self.headers) - buf.write(struct.pack('!BBB', self.delivery_mode, self.priority, len(self.correlation_id))) - buf.write(self.correlation_id) - buf.write(struct.pack('!B', len(self.reply_to))) - buf.write(self.reply_to) - buf.write(struct.pack('!B', len(self.expiration))) - buf.write(self.expiration) - buf.write(struct.pack('!B', len(self.message_id))) - buf.write(self.message_id) - buf.write(struct.pack('!LB', self.timestamp, len(self.type))) - buf.write(self.type) - buf.write(struct.pack('!B', len(self.user_id))) - buf.write(self.user_id) - buf.write(struct.pack('!B', len(self.app_id))) - buf.write(self.app_id) - buf.write(b'\x00') - - @staticmethod - def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= QueueUnbindOk.MINIMUM_SIZE, 'Content property list too short!' - offset = start_offset # we will use it to count consumed bytes - s_len, = struct.unpack_from('!B', buf, offset) - offset += 1 - content_type = buf[offset:offset+s_len] - offset += s_len - s_len, = struct.unpack_from('!B', buf, offset) - offset += 1 - content_encoding = buf[offset:offset+s_len] - offset += s_len - headers, delta = deframe_table(buf, offset) - offset += delta - delivery_mode, priority, s_len, = struct.unpack_from('!BBB', buf, offset) - offset += 3 - correlation_id = buf[offset:offset+s_len] - offset += s_len - s_len, = struct.unpack_from('!B', buf, offset) - offset += 1 - reply_to = buf[offset:offset+s_len] - offset += s_len - s_len, = struct.unpack_from('!B', buf, offset) - offset += 1 - expiration = buf[offset:offset+s_len] - offset += s_len - s_len, = struct.unpack_from('!B', buf, offset) - offset += 1 - message_id = buf[offset:offset+s_len] - offset += s_len - timestamp, s_len, = struct.unpack_from('!LB', buf, offset) - offset += 9 - type = buf[offset:offset+s_len] - offset += s_len - s_len, = struct.unpack_from('!B', buf, offset) - offset += 1 - user_id = buf[offset:offset+s_len] - offset += s_len - s_len, = struct.unpack_from('!B', buf, offset) - offset += 1 - app_id = buf[offset:offset+s_len] - offset += s_len - s_len, = struct.unpack_from('!B', buf, offset) - offset += 1 - offset += s_len - return BasicContentPropertyList(content_type, content_encoding, headers, delivery_mode, priority, correlation_id, reply_to, expiration, message_id, timestamp, type, user_id, app_id) - - def get_size(self): - return len(self.content_type) + len(self.content_encoding) + frame_table_size(self.headers) + len(self.correlation_id) + len(self.reply_to) + len(self.expiration) + len(self.message_id) + len(self.type) + len(self.user_id) + len(self.app_id) + 24 - class BasicAck(AMQPMethodPayload): """ @@ -2512,32 +1913,21 @@ class BasicAck(AMQPMethodPayload): methods. The client can ask to confirm a single message or a set of messages up to and including a specific message. """ - CLASS = Basic - NAME = u'ack' - CLASSNAME = u'basic' - FULLNAME = u'basic.ack' + NAME = u'basic.ack' - CONTENT_PROPERTY_LIST = BasicContentPropertyList - - CLASS_INDEX = 60 - CLASS_INDEX_BINARY = b'\x3C' - METHOD_INDEX = 80 - METHOD_INDEX_BINARY = b'\x50' + INDEX = (60, 80) # (Class ID, Method ID) BINARY_HEADER = b'\x3C\x50' # CLASS ID + METHOD ID - SYNCHRONOUS = False # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 9 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'delivery-tag', u'delivery-tag', u'longlong', False), - (u'multiple', u'bit', u'bit', False), # acknowledge multiple messages + + # See constructor pydoc for details + FIELDS = [ + Field(u'delivery-tag', u'longlong', reserved=False), + Field(u'multiple', u'bit', reserved=False), ] def __init__(self, delivery_tag, multiple): @@ -2556,18 +1946,19 @@ class BasicAck(AMQPMethodPayload): self.multiple = multiple def write_arguments(self, buf): - buf.write(struct.pack('!Q', self.delivery_tag)) - buf.write(struct.pack('!B', (int(self.multiple) << 0))) - + buf.write(struct.pack('!QB', self.delivery_tag, (self.multiple << 0))) + def get_size(self): return 9 @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= BasicAck.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes - delivery_tag, _bit_0, = struct.unpack_from('!QB', buf, offset) - multiple = bool(_bit_0 & 1) + offset = start_offset + _bit, = struct.unpack_from('!B', buf, offset) + multiple = bool(_bit >> 0) + offset += 1 + delivery_tag, = struct.unpack_from('!Q', buf, offset) + offset += 8 return BasicAck(delivery_tag, multiple) @@ -2579,38 +1970,27 @@ class BasicConsume(AMQPMethodPayload): messages from a specific queue. Consumers last as long as the channel they were declared on, or until the client cancels them. """ - CLASS = Basic - NAME = u'consume' - CLASSNAME = u'basic' - FULLNAME = u'basic.consume' - - CONTENT_PROPERTY_LIST = BasicContentPropertyList + NAME = u'basic.consume' - CLASS_INDEX = 60 - CLASS_INDEX_BINARY = b'\x3C' - METHOD_INDEX = 20 - METHOD_INDEX_BINARY = b'\x14' + INDEX = (60, 20) # (Class ID, Method ID) BINARY_HEADER = b'\x3C\x14' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [BasicConsumeOk] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 36 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'reserved-1', u'short', u'short', True), - (u'queue', u'queue-name', u'shortstr', False), - (u'consumer-tag', u'consumer-tag', u'shortstr', False), - (u'no-local', u'no-local', u'bit', False), - (u'no-ack', u'no-ack', u'bit', False), - (u'exclusive', u'bit', u'bit', False), # request exclusive access - (u'no-wait', u'no-wait', u'bit', False), - (u'arguments', u'table', u'table', False), # arguments for declaration + + # 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), ] def __init__(self, queue, consumer_tag, no_local, no_ack, exclusive, no_wait, arguments): @@ -2633,7 +2013,7 @@ class BasicConsume(AMQPMethodPayload): :param arguments: Arguments for declaration A set of arguments for the consume. The syntax and semantics of these arguments depends on the server implementation. - :type arguments: table. See coolamqp.uplink.frames.field_table (table in AMQP) + :type arguments: table. See coolamqp.uplink.framing.field_table (table in AMQP) """ self.queue = queue self.consumer_tag = consumer_tag @@ -2644,21 +2024,19 @@ class BasicConsume(AMQPMethodPayload): self.arguments = arguments def write_arguments(self, buf): - buf.write(b'\x00\x00') - buf.write(struct.pack('!B', len(self.queue))) + buf.write(struct.pack('!HB', self.reserved_1, len(self.queue))) buf.write(self.queue) buf.write(struct.pack('!B', len(self.consumer_tag))) buf.write(self.consumer_tag) - buf.write(struct.pack('!B', (int(self.no_local) << 0) | (int(self.no_ack) << 1) | (int(self.exclusive) << 2) | (int(self.no_wait) << 3))) enframe_table(buf, self.arguments) - + buf.write(struct.pack('!B', (self.no_local << 0) | (self.no_ack << 1) | (self.exclusive << 2) | (self.no_wait << 3))) + def get_size(self): - return len(self.queue) + len(self.consumer_tag) + frame_table_size(self.arguments) + 9 + return 9 + len(self.queue) + len(self.consumer_tag) + frame_table_size(self.arguments) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= BasicConsume.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset s_len, = struct.unpack_from('!2xB', buf, offset) offset += 3 queue = buf[offset:offset+s_len] @@ -2668,11 +2046,11 @@ class BasicConsume(AMQPMethodPayload): consumer_tag = buf[offset:offset+s_len] offset += s_len _bit, = struct.unpack_from('!B', buf, offset) + no_local = bool(_bit >> 0) + no_ack = bool(_bit >> 1) + exclusive = bool(_bit >> 2) + no_wait = bool(_bit >> 3) offset += 1 - no_local = bool(_bit & 1) - no_ack = bool(_bit & 2) - exclusive = bool(_bit & 4) - no_wait = bool(_bit & 8) arguments, delta = deframe_table(buf, offset) offset += delta return BasicConsume(queue, consumer_tag, no_local, no_ack, exclusive, no_wait, arguments) @@ -2687,32 +2065,21 @@ class BasicCancel(AMQPMethodPayload): that consumer. The client may receive an arbitrary number of messages in between sending the cancel method and receiving the cancel-ok reply. """ - CLASS = Basic - NAME = u'cancel' - CLASSNAME = u'basic' - FULLNAME = u'basic.cancel' - - CONTENT_PROPERTY_LIST = BasicContentPropertyList + NAME = u'basic.cancel' - CLASS_INDEX = 60 - CLASS_INDEX_BINARY = b'\x3C' - METHOD_INDEX = 30 - METHOD_INDEX_BINARY = b'\x1E' + INDEX = (60, 30) # (Class ID, Method ID) BINARY_HEADER = b'\x3C\x1E' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [BasicCancelOk] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 8 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'consumer-tag', u'consumer-tag', u'shortstr', False), - (u'no-wait', u'no-wait', u'bit', False), + + # See constructor pydoc for details + FIELDS = [ + Field(u'consumer-tag', u'shortstr', reserved=False), + Field(u'no-wait', u'bit', reserved=False), ] def __init__(self, consumer_tag, no_wait): @@ -2728,22 +2095,21 @@ class BasicCancel(AMQPMethodPayload): def write_arguments(self, buf): buf.write(struct.pack('!B', len(self.consumer_tag))) buf.write(self.consumer_tag) - buf.write(struct.pack('!B', (int(self.no_wait) << 0))) - + buf.write(struct.pack('!B', (self.no_wait << 0))) + def get_size(self): - return len(self.consumer_tag) + 2 + return 2 + len(self.consumer_tag) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= BasicCancel.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset s_len, = struct.unpack_from('!B', buf, offset) offset += 1 consumer_tag = buf[offset:offset+s_len] offset += s_len _bit, = struct.unpack_from('!B', buf, offset) + no_wait = bool(_bit >> 0) offset += 1 - no_wait = bool(_bit & 1) return BasicCancel(consumer_tag, no_wait) @@ -2754,32 +2120,21 @@ class BasicConsumeOk(AMQPMethodPayload): The server provides the client with a consumer tag, which is used by the client for methods called on the consumer at a later stage. """ - CLASS = Basic - NAME = u'consume-ok' - CLASSNAME = u'basic' - FULLNAME = u'basic.consume-ok' - - CONTENT_PROPERTY_LIST = BasicContentPropertyList + NAME = u'basic.consume-ok' - CLASS_INDEX = 60 - CLASS_INDEX_BINARY = b'\x3C' - METHOD_INDEX = 21 - METHOD_INDEX_BINARY = b'\x15' + INDEX = (60, 21) # (Class ID, Method ID) BINARY_HEADER = b'\x3C\x15' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 7 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'consumer-tag', u'consumer-tag', u'shortstr', False), + + # See constructor pydoc for details + FIELDS = [ + Field(u'consumer-tag', u'shortstr', reserved=False), ] def __init__(self, consumer_tag): @@ -2794,14 +2149,13 @@ class BasicConsumeOk(AMQPMethodPayload): def write_arguments(self, buf): buf.write(struct.pack('!B', len(self.consumer_tag))) buf.write(self.consumer_tag) - + def get_size(self): - return len(self.consumer_tag) + 1 + return 1 + len(self.consumer_tag) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= BasicConsumeOk.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset s_len, = struct.unpack_from('!B', buf, offset) offset += 1 consumer_tag = buf[offset:offset+s_len] @@ -2815,32 +2169,21 @@ class BasicCancelOk(AMQPMethodPayload): This method confirms that the cancellation was completed. """ - CLASS = Basic - NAME = u'cancel-ok' - CLASSNAME = u'basic' - FULLNAME = u'basic.cancel-ok' + NAME = u'basic.cancel-ok' - CONTENT_PROPERTY_LIST = BasicContentPropertyList - - CLASS_INDEX = 60 - CLASS_INDEX_BINARY = b'\x3C' - METHOD_INDEX = 31 - METHOD_INDEX_BINARY = b'\x1F' + INDEX = (60, 31) # (Class ID, Method ID) BINARY_HEADER = b'\x3C\x1F' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 7 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'consumer-tag', u'consumer-tag', u'shortstr', False), + + # See constructor pydoc for details + FIELDS = [ + Field(u'consumer-tag', u'shortstr', reserved=False), ] def __init__(self, consumer_tag): @@ -2854,14 +2197,13 @@ class BasicCancelOk(AMQPMethodPayload): def write_arguments(self, buf): buf.write(struct.pack('!B', len(self.consumer_tag))) buf.write(self.consumer_tag) - + def get_size(self): - return len(self.consumer_tag) + 1 + return 1 + len(self.consumer_tag) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= BasicCancelOk.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset s_len, = struct.unpack_from('!B', buf, offset) offset += 1 consumer_tag = buf[offset:offset+s_len] @@ -2878,35 +2220,24 @@ class BasicDeliver(AMQPMethodPayload): the server responds with Deliver methods as and when messages arrive for that consumer. """ - CLASS = Basic - NAME = u'deliver' - CLASSNAME = u'basic' - FULLNAME = u'basic.deliver' + NAME = u'basic.deliver' - CONTENT_PROPERTY_LIST = BasicContentPropertyList - - CLASS_INDEX = 60 - CLASS_INDEX_BINARY = b'\x3C' - METHOD_INDEX = 60 - METHOD_INDEX_BINARY = b'\x3C' + INDEX = (60, 60) # (Class ID, Method ID) BINARY_HEADER = b'\x3C\x3C' # CLASS ID + METHOD ID - SYNCHRONOUS = False # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 30 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'consumer-tag', u'consumer-tag', u'shortstr', False), - (u'delivery-tag', u'delivery-tag', u'longlong', False), - (u'redelivered', u'redelivered', u'bit', False), - (u'exchange', u'exchange-name', u'shortstr', False), - (u'routing-key', u'shortstr', u'shortstr', False), # Message routing key + + # 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), ] def __init__(self, consumer_tag, delivery_tag, redelivered, exchange, routing_key): @@ -2932,26 +2263,24 @@ class BasicDeliver(AMQPMethodPayload): def write_arguments(self, buf): buf.write(struct.pack('!B', len(self.consumer_tag))) buf.write(self.consumer_tag) - buf.write(struct.pack('!B', (int(self.redelivered) << 0))) - buf.write(struct.pack('!QB', self.delivery_tag, len(self.exchange))) + buf.write(struct.pack('!QBB', self.delivery_tag, (self.redelivered << 0), len(self.exchange))) buf.write(self.exchange) buf.write(struct.pack('!B', len(self.routing_key))) buf.write(self.routing_key) - + def get_size(self): - return len(self.consumer_tag) + len(self.exchange) + len(self.routing_key) + 12 + return 12 + len(self.consumer_tag) + len(self.exchange) + len(self.routing_key) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= BasicDeliver.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset s_len, = struct.unpack_from('!B', buf, offset) offset += 1 consumer_tag = buf[offset:offset+s_len] offset += s_len _bit, = struct.unpack_from('!B', buf, offset) + redelivered = bool(_bit >> 0) offset += 1 - redelivered = bool(_bit & 1) delivery_tag, s_len, = struct.unpack_from('!QB', buf, offset) offset += 9 exchange = buf[offset:offset+s_len] @@ -2971,33 +2300,22 @@ class BasicGet(AMQPMethodPayload): dialogue that is designed for specific types of application where synchronous functionality is more important than performance. """ - CLASS = Basic - NAME = u'get' - CLASSNAME = u'basic' - FULLNAME = u'basic.get' + NAME = u'basic.get' - CONTENT_PROPERTY_LIST = BasicContentPropertyList - - CLASS_INDEX = 60 - CLASS_INDEX_BINARY = b'\x3C' - METHOD_INDEX = 70 - METHOD_INDEX_BINARY = b'\x46' + INDEX = (60, 70) # (Class ID, Method ID) BINARY_HEADER = b'\x3C\x46' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [BasicGetOk, BasicGetEmpty] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 10 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'reserved-1', u'short', u'short', True), - (u'queue', u'queue-name', u'shortstr', False), - (u'no-ack', u'no-ack', u'bit', False), + + # 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), ] def __init__(self, queue, no_ack): @@ -3012,25 +2330,23 @@ class BasicGet(AMQPMethodPayload): self.no_ack = no_ack def write_arguments(self, buf): - buf.write(b'\x00\x00') - buf.write(struct.pack('!B', len(self.queue))) + buf.write(struct.pack('!HB', self.reserved_1, len(self.queue))) buf.write(self.queue) - buf.write(struct.pack('!B', (int(self.no_ack) << 0))) - + buf.write(struct.pack('!B', (self.no_ack << 0))) + def get_size(self): - return len(self.queue) + 4 + return 4 + len(self.queue) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= BasicGet.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset s_len, = struct.unpack_from('!2xB', buf, offset) offset += 3 queue = buf[offset:offset+s_len] offset += s_len _bit, = struct.unpack_from('!B', buf, offset) + no_ack = bool(_bit >> 0) offset += 1 - no_ack = bool(_bit & 1) return BasicGet(queue, no_ack) @@ -3042,36 +2358,25 @@ class BasicGetOk(AMQPMethodPayload): delivered by 'get-ok' must be acknowledged unless the no-ack option was set in the get method. """ - CLASS = Basic - NAME = u'get-ok' - CLASSNAME = u'basic' - FULLNAME = u'basic.get-ok' + NAME = u'basic.get-ok' - CONTENT_PROPERTY_LIST = BasicContentPropertyList - - CLASS_INDEX = 60 - CLASS_INDEX_BINARY = b'\x3C' - METHOD_INDEX = 71 - METHOD_INDEX_BINARY = b'\x47' + INDEX = (60, 71) # (Class ID, Method ID) BINARY_HEADER = b'\x3C\x47' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 27 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'delivery-tag', u'delivery-tag', u'longlong', False), - (u'redelivered', u'redelivered', u'bit', False), - (u'exchange', u'exchange-name', u'shortstr', False), - (u'routing-key', u'shortstr', u'shortstr', False), # Message routing key - (u'message-count', u'message-count', u'long', False), + + # 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), ] def __init__(self, delivery_tag, redelivered, exchange, routing_key, message_count): @@ -3095,23 +2400,21 @@ class BasicGetOk(AMQPMethodPayload): self.message_count = message_count def write_arguments(self, buf): - buf.write(struct.pack('!B', (int(self.redelivered) << 0))) - buf.write(struct.pack('!QB', self.delivery_tag, len(self.exchange))) + buf.write(struct.pack('!QBB', self.delivery_tag, (self.redelivered << 0), len(self.exchange))) buf.write(self.exchange) buf.write(struct.pack('!B', len(self.routing_key))) buf.write(self.routing_key) buf.write(struct.pack('!I', self.message_count)) - + def get_size(self): - return len(self.exchange) + len(self.routing_key) + 15 + return 15 + len(self.exchange) + len(self.routing_key) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= BasicGetOk.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset _bit, = struct.unpack_from('!B', buf, offset) + redelivered = bool(_bit >> 0) offset += 1 - redelivered = bool(_bit & 1) delivery_tag, s_len, = struct.unpack_from('!QB', buf, offset) offset += 9 exchange = buf[offset:offset+s_len] @@ -3132,33 +2435,22 @@ class BasicGetEmpty(AMQPMethodPayload): This method tells the client that the queue has no messages available for the client. """ - CLASS = Basic - NAME = u'get-empty' - CLASSNAME = u'basic' - FULLNAME = u'basic.get-empty' - - CONTENT_PROPERTY_LIST = BasicContentPropertyList + NAME = u'basic.get-empty' - CLASS_INDEX = 60 - CLASS_INDEX_BINARY = b'\x3C' - METHOD_INDEX = 72 - METHOD_INDEX_BINARY = b'\x48' + INDEX = (60, 72) # (Class ID, Method ID) BINARY_HEADER = b'\x3C\x48' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 7 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'reserved-1', u'shortstr', u'shortstr', True), + + # See constructor pydoc for details + FIELDS = [ + Field(u'reserved-1', u'shortstr', reserved=True), ] def __init__(self): @@ -3166,10 +2458,13 @@ class BasicGetEmpty(AMQPMethodPayload): Create frame basic.get-empty """ - # not generating write_arguments - this method has static content! @staticmethod def from_buffer(buf, start_offset): + offset = start_offset + s_len, = struct.unpack_from('!B', buf, offset) + offset += 1 + offset += s_len # reserved field! return BasicGetEmpty() @@ -3181,35 +2476,24 @@ class BasicPublish(AMQPMethodPayload): to queues as defined by the exchange configuration and distributed to any active consumers when the transaction, if any, is committed. """ - CLASS = Basic - NAME = u'publish' - CLASSNAME = u'basic' - FULLNAME = u'basic.publish' + NAME = u'basic.publish' - CONTENT_PROPERTY_LIST = BasicContentPropertyList - - CLASS_INDEX = 60 - CLASS_INDEX_BINARY = b'\x3C' - METHOD_INDEX = 40 - METHOD_INDEX_BINARY = b'\x28' + INDEX = (60, 40) # (Class ID, Method ID) BINARY_HEADER = b'\x3C\x28' # CLASS ID + METHOD ID - SYNCHRONOUS = False # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 17 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'reserved-1', u'short', u'short', True), - (u'exchange', u'exchange-name', u'shortstr', False), - (u'routing-key', u'shortstr', u'shortstr', False), # Message routing key - (u'mandatory', u'bit', u'bit', False), # indicate mandatory routing - (u'immediate', u'bit', u'bit', False), # request immediate delivery + + # 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), ] def __init__(self, exchange, routing_key, mandatory, immediate): @@ -3242,20 +2526,18 @@ class BasicPublish(AMQPMethodPayload): self.immediate = immediate def write_arguments(self, buf): - buf.write(b'\x00\x00') - buf.write(struct.pack('!B', len(self.exchange))) + buf.write(struct.pack('!HB', self.reserved_1, len(self.exchange))) buf.write(self.exchange) buf.write(struct.pack('!B', len(self.routing_key))) buf.write(self.routing_key) - buf.write(struct.pack('!B', (int(self.mandatory) << 0) | (int(self.immediate) << 1))) - + buf.write(struct.pack('!B', (self.mandatory << 0) | (self.immediate << 1))) + def get_size(self): - return len(self.exchange) + len(self.routing_key) + 5 + return 5 + len(self.exchange) + len(self.routing_key) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= BasicPublish.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset s_len, = struct.unpack_from('!2xB', buf, offset) offset += 3 exchange = buf[offset:offset+s_len] @@ -3265,9 +2547,9 @@ class BasicPublish(AMQPMethodPayload): routing_key = buf[offset:offset+s_len] offset += s_len _bit, = struct.unpack_from('!B', buf, offset) + mandatory = bool(_bit >> 0) + immediate = bool(_bit >> 1) offset += 1 - mandatory = bool(_bit & 1) - immediate = bool(_bit & 2) return BasicPublish(exchange, routing_key, mandatory, immediate) @@ -3281,33 +2563,22 @@ class BasicQos(AMQPMethodPayload): qos method could in principle apply to both peers, it is currently meaningful only for the server. """ - CLASS = Basic - NAME = u'qos' - CLASSNAME = u'basic' - FULLNAME = u'basic.qos' - - CONTENT_PROPERTY_LIST = BasicContentPropertyList + NAME = u'basic.qos' - CLASS_INDEX = 60 - CLASS_INDEX_BINARY = b'\x3C' - METHOD_INDEX = 10 - METHOD_INDEX_BINARY = b'\x0A' + INDEX = (60, 10) # (Class ID, Method ID) BINARY_HEADER = b'\x3C\x0A' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [BasicQosOk] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 7 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'prefetch-size', u'long', u'long', False), # prefetch window in octets - (u'prefetch-count', u'short', u'short', False), # prefetch window in messages - (u'global', u'bit', u'bit', False), # apply to entire connection + + # 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), ] def __init__(self, prefetch_size, prefetch_count, global_): @@ -3340,18 +2611,19 @@ class BasicQos(AMQPMethodPayload): self.global_ = global_ def write_arguments(self, buf): - buf.write(struct.pack('!IH', self.prefetch_size, self.prefetch_count)) - buf.write(struct.pack('!B', (int(self.global_) << 0))) - + buf.write(struct.pack('!IHB', self.prefetch_size, self.prefetch_count, (self.global_ << 0))) + def get_size(self): return 7 @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= BasicQos.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes - prefetch_size, prefetch_count, _bit_0, = struct.unpack_from('!IHB', buf, offset) - global_ = bool(_bit_0 & 1) + offset = start_offset + _bit, = struct.unpack_from('!B', buf, offset) + global_ = bool(_bit >> 0) + offset += 1 + prefetch_size, prefetch_count, = struct.unpack_from('!IH', buf, offset) + offset += 6 return BasicQos(prefetch_size, prefetch_count, global_) @@ -3363,26 +2635,13 @@ class BasicQosOk(AMQPMethodPayload): server. The requested QoS applies to all active consumers until a new QoS is defined. """ - CLASS = Basic - NAME = u'qos-ok' - CLASSNAME = u'basic' - FULLNAME = u'basic.qos-ok' - - CONTENT_PROPERTY_LIST = BasicContentPropertyList + NAME = u'basic.qos-ok' - CLASS_INDEX = 60 - CLASS_INDEX_BINARY = b'\x3C' - METHOD_INDEX = 11 - METHOD_INDEX_BINARY = b'\x0B' + INDEX = (60, 11) # (Class ID, Method ID) BINARY_HEADER = b'\x3C\x0B' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 0 # arguments part can never be shorter than this + 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 @@ -3394,10 +2653,10 @@ class BasicQosOk(AMQPMethodPayload): Create frame basic.qos-ok """ - # not generating write_arguments - this method has static content! @staticmethod def from_buffer(buf, start_offset): + offset = start_offset return BasicQosOk() @@ -3410,34 +2669,23 @@ class BasicReturn(AMQPMethodPayload): reply code and text provide information about the reason that the message was undeliverable. """ - CLASS = Basic - NAME = u'return' - CLASSNAME = u'basic' - FULLNAME = u'basic.return' - - CONTENT_PROPERTY_LIST = BasicContentPropertyList + NAME = u'basic.return' - CLASS_INDEX = 60 - CLASS_INDEX_BINARY = b'\x3C' - METHOD_INDEX = 50 - METHOD_INDEX_BINARY = b'\x32' + INDEX = (60, 50) # (Class ID, Method ID) BINARY_HEADER = b'\x3C\x32' # CLASS ID + METHOD ID - SYNCHRONOUS = False # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 23 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'reply-code', u'reply-code', u'short', False), - (u'reply-text', u'reply-text', u'shortstr', False), - (u'exchange', u'exchange-name', u'shortstr', False), - (u'routing-key', u'shortstr', u'shortstr', False), # Message routing key + + # 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), ] def __init__(self, reply_code, reply_text, exchange, routing_key): @@ -3465,14 +2713,13 @@ class BasicReturn(AMQPMethodPayload): buf.write(self.exchange) buf.write(struct.pack('!B', len(self.routing_key))) buf.write(self.routing_key) - + def get_size(self): - return len(self.reply_text) + len(self.exchange) + len(self.routing_key) + 5 + return 5 + len(self.reply_text) + len(self.exchange) + len(self.routing_key) @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= BasicReturn.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes + offset = start_offset reply_code, s_len, = struct.unpack_from('!HB', buf, offset) offset += 3 reply_text = buf[offset:offset+s_len] @@ -3496,32 +2743,21 @@ class BasicReject(AMQPMethodPayload): cancel large incoming messages, or return untreatable messages to their original queue. """ - CLASS = Basic - NAME = u'reject' - CLASSNAME = u'basic' - FULLNAME = u'basic.reject' - - CONTENT_PROPERTY_LIST = BasicContentPropertyList + NAME = u'basic.reject' - CLASS_INDEX = 60 - CLASS_INDEX_BINARY = b'\x3C' - METHOD_INDEX = 90 - METHOD_INDEX_BINARY = b'\x5A' + INDEX = (60, 90) # (Class ID, Method ID) BINARY_HEADER = b'\x3C\x5A' # CLASS ID + METHOD ID - SYNCHRONOUS = False # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 9 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'delivery-tag', u'delivery-tag', u'longlong', False), - (u'requeue', u'bit', u'bit', False), # requeue the message + + # See constructor pydoc for details + FIELDS = [ + Field(u'delivery-tag', u'longlong', reserved=False), + Field(u'requeue', u'bit', reserved=False), ] def __init__(self, delivery_tag, requeue): @@ -3538,18 +2774,19 @@ class BasicReject(AMQPMethodPayload): self.requeue = requeue def write_arguments(self, buf): - buf.write(struct.pack('!Q', self.delivery_tag)) - buf.write(struct.pack('!B', (int(self.requeue) << 0))) - + buf.write(struct.pack('!QB', self.delivery_tag, (self.requeue << 0))) + def get_size(self): return 9 @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= BasicReject.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes - delivery_tag, _bit_0, = struct.unpack_from('!QB', buf, offset) - requeue = bool(_bit_0 & 1) + offset = start_offset + _bit, = struct.unpack_from('!B', buf, offset) + requeue = bool(_bit >> 0) + offset += 1 + delivery_tag, = struct.unpack_from('!Q', buf, offset) + offset += 8 return BasicReject(delivery_tag, requeue) @@ -3561,31 +2798,20 @@ class BasicRecoverAsync(AMQPMethodPayload): specified channel. Zero or more messages may be redelivered. This method is deprecated in favour of the synchronous Recover/Recover-Ok. """ - CLASS = Basic - NAME = u'recover-async' - CLASSNAME = u'basic' - FULLNAME = u'basic.recover-async' + NAME = u'basic.recover-async' - CONTENT_PROPERTY_LIST = BasicContentPropertyList - - CLASS_INDEX = 60 - CLASS_INDEX_BINARY = b'\x3C' - METHOD_INDEX = 100 - METHOD_INDEX_BINARY = b'\x64' + INDEX = (60, 100) # (Class ID, Method ID) BINARY_HEADER = b'\x3C\x64' # CLASS ID + METHOD ID - SYNCHRONOUS = False # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 1 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'requeue', u'bit', u'bit', False), # requeue the message + + # See constructor pydoc for details + FIELDS = [ + Field(u'requeue', u'bit', reserved=False), ] def __init__(self, requeue): @@ -3601,17 +2827,17 @@ class BasicRecoverAsync(AMQPMethodPayload): self.requeue = requeue def write_arguments(self, buf): - buf.write(struct.pack('!B', (int(self.requeue) << 0))) - + buf.write(struct.pack('!B', (self.requeue << 0))) + def get_size(self): return 1 @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= BasicRecoverAsync.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes - _bit_0, = struct.unpack_from('!B', buf, offset) - requeue = bool(_bit_0 & 1) + offset = start_offset + _bit, = struct.unpack_from('!B', buf, offset) + requeue = bool(_bit >> 0) + offset += 1 return BasicRecoverAsync(requeue) @@ -3623,31 +2849,20 @@ class BasicRecover(AMQPMethodPayload): specified channel. Zero or more messages may be redelivered. This method replaces the asynchronous Recover. """ - CLASS = Basic - NAME = u'recover' - CLASSNAME = u'basic' - FULLNAME = u'basic.recover' + NAME = u'basic.recover' - CONTENT_PROPERTY_LIST = BasicContentPropertyList - - CLASS_INDEX = 60 - CLASS_INDEX_BINARY = b'\x3C' - METHOD_INDEX = 110 - METHOD_INDEX_BINARY = b'\x6E' + INDEX = (60, 110) # (Class ID, Method ID) BINARY_HEADER = b'\x3C\x6E' # CLASS ID + METHOD ID - SYNCHRONOUS = False # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 1 # arguments part can never be shorter than this + 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 - FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved) - (u'requeue', u'bit', u'bit', False), # requeue the message + + # See constructor pydoc for details + FIELDS = [ + Field(u'requeue', u'bit', reserved=False), ] def __init__(self, requeue): @@ -3663,17 +2878,17 @@ class BasicRecover(AMQPMethodPayload): self.requeue = requeue def write_arguments(self, buf): - buf.write(struct.pack('!B', (int(self.requeue) << 0))) - + buf.write(struct.pack('!B', (self.requeue << 0))) + def get_size(self): return 1 @staticmethod def from_buffer(buf, start_offset): - assert (len(buf) - start_offset) >= BasicRecover.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes - _bit_0, = struct.unpack_from('!B', buf, offset) - requeue = bool(_bit_0 & 1) + offset = start_offset + _bit, = struct.unpack_from('!B', buf, offset) + requeue = bool(_bit >> 0) + offset += 1 return BasicRecover(requeue) @@ -3683,26 +2898,13 @@ class BasicRecoverOk(AMQPMethodPayload): This method acknowledges a Basic.Recover method. """ - CLASS = Basic - NAME = u'recover-ok' - CLASSNAME = u'basic' - FULLNAME = u'basic.recover-ok' - - CONTENT_PROPERTY_LIST = BasicContentPropertyList + NAME = u'basic.recover-ok' - CLASS_INDEX = 60 - CLASS_INDEX_BINARY = b'\x3C' - METHOD_INDEX = 111 - METHOD_INDEX_BINARY = b'\x6F' + INDEX = (60, 111) # (Class ID, Method ID) BINARY_HEADER = b'\x3C\x6F' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 0 # arguments part can never be shorter than this + 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 @@ -3713,10 +2915,10 @@ class BasicRecoverOk(AMQPMethodPayload): Create frame basic.recover-ok """ - # not generating write_arguments - this method has static content! @staticmethod def from_buffer(buf, start_offset): + offset = start_offset return BasicRecoverOk() @@ -3737,44 +2939,6 @@ class Tx(AMQPClass): INDEX = 90 -class TxContentPropertyList(AMQPContentPropertyList): - """ - The tx class allows publish and ack operations to be batched into atomic - - units of work. The intention is that all publish and ack requests issued - within a transaction will complete successfully or none of them will. - Servers SHOULD implement atomic transactions at least where all publish - or ack requests affect a single queue. Transactions that cover multiple - queues may be non-atomic, given that queues can be created and destroyed - asynchronously, and such events do not form part of any transaction. - Further, the behaviour of transactions with respect to the immediate and - mandatory flags on Basic.Publish methods is not defined. - """ - CLASS_NAME = u'tx' - CLASS_INDEX = 90 - CLASS = Tx - - CONTENT_PROPERTIES = [ # tuple of (name, domain, type) - - ] - - def __init__(self): - """ - Create the property list. - - """ - - def write_arguments(self, buf): - pass # this has a frame, but its only default shortstrs - - @staticmethod - def from_buffer(buf, start_offset): - return TxContentPropertyList() - - def get_size(self): - return 0 - - class TxCommit(AMQPMethodPayload): """ Commit the current transaction @@ -3782,26 +2946,13 @@ class TxCommit(AMQPMethodPayload): This method commits all message publications and acknowledgments performed in the current transaction. A new transaction starts immediately after a commit. """ - CLASS = Tx - NAME = u'commit' - CLASSNAME = u'tx' - FULLNAME = u'tx.commit' - - CONTENT_PROPERTY_LIST = TxContentPropertyList + NAME = u'tx.commit' - CLASS_INDEX = 90 - CLASS_INDEX_BINARY = b'\x5A' - METHOD_INDEX = 20 - METHOD_INDEX_BINARY = b'\x14' + INDEX = (90, 20) # (Class ID, Method ID) BINARY_HEADER = b'\x5A\x14' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [TxCommitOk] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 0 # arguments part can never be shorter than this + 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 @@ -3812,10 +2963,10 @@ class TxCommit(AMQPMethodPayload): Create frame tx.commit """ - # not generating write_arguments - this method has static content! @staticmethod def from_buffer(buf, start_offset): + offset = start_offset return TxCommit() @@ -3826,26 +2977,13 @@ class TxCommitOk(AMQPMethodPayload): This method confirms to the client that the commit succeeded. Note that if a commit fails, the server raises a channel exception. """ - CLASS = Tx - NAME = u'commit-ok' - CLASSNAME = u'tx' - FULLNAME = u'tx.commit-ok' + NAME = u'tx.commit-ok' - CONTENT_PROPERTY_LIST = TxContentPropertyList - - CLASS_INDEX = 90 - CLASS_INDEX_BINARY = b'\x5A' - METHOD_INDEX = 21 - METHOD_INDEX_BINARY = b'\x15' + INDEX = (90, 21) # (Class ID, Method ID) BINARY_HEADER = b'\x5A\x15' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 0 # arguments part can never be shorter than this + 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 @@ -3857,10 +2995,10 @@ class TxCommitOk(AMQPMethodPayload): Create frame tx.commit-ok """ - # not generating write_arguments - this method has static content! @staticmethod def from_buffer(buf, start_offset): + offset = start_offset return TxCommitOk() @@ -3873,26 +3011,13 @@ class TxRollback(AMQPMethodPayload): Note that unacked messages will not be automatically redelivered by rollback; if that is required an explicit recover call should be issued. """ - CLASS = Tx - NAME = u'rollback' - CLASSNAME = u'tx' - FULLNAME = u'tx.rollback' - - CONTENT_PROPERTY_LIST = TxContentPropertyList + NAME = u'tx.rollback' - CLASS_INDEX = 90 - CLASS_INDEX_BINARY = b'\x5A' - METHOD_INDEX = 30 - METHOD_INDEX_BINARY = b'\x1E' + INDEX = (90, 30) # (Class ID, Method ID) BINARY_HEADER = b'\x5A\x1E' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [TxRollbackOk] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 0 # arguments part can never be shorter than this + 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 @@ -3903,10 +3028,10 @@ class TxRollback(AMQPMethodPayload): Create frame tx.rollback """ - # not generating write_arguments - this method has static content! @staticmethod def from_buffer(buf, start_offset): + offset = start_offset return TxRollback() @@ -3917,26 +3042,13 @@ class TxRollbackOk(AMQPMethodPayload): This method confirms to the client that the rollback succeeded. Note that if an rollback fails, the server raises a channel exception. """ - CLASS = Tx - NAME = u'rollback-ok' - CLASSNAME = u'tx' - FULLNAME = u'tx.rollback-ok' - - CONTENT_PROPERTY_LIST = TxContentPropertyList + NAME = u'tx.rollback-ok' - CLASS_INDEX = 90 - CLASS_INDEX_BINARY = b'\x5A' - METHOD_INDEX = 31 - METHOD_INDEX_BINARY = b'\x1F' + INDEX = (90, 31) # (Class ID, Method ID) BINARY_HEADER = b'\x5A\x1F' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 0 # arguments part can never be shorter than this + 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 @@ -3948,10 +3060,10 @@ class TxRollbackOk(AMQPMethodPayload): Create frame tx.rollback-ok """ - # not generating write_arguments - this method has static content! @staticmethod def from_buffer(buf, start_offset): + offset = start_offset return TxRollbackOk() @@ -3962,26 +3074,13 @@ class TxSelect(AMQPMethodPayload): This method sets the channel to use standard transactions. The client must use this method at least once on a channel before using the Commit or Rollback methods. """ - CLASS = Tx - NAME = u'select' - CLASSNAME = u'tx' - FULLNAME = u'tx.select' + NAME = u'tx.select' - CONTENT_PROPERTY_LIST = TxContentPropertyList - - CLASS_INDEX = 90 - CLASS_INDEX_BINARY = b'\x5A' - METHOD_INDEX = 10 - METHOD_INDEX_BINARY = b'\x0A' + INDEX = (90, 10) # (Class ID, Method ID) BINARY_HEADER = b'\x5A\x0A' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [TxSelectOk] - - SENT_BY_CLIENT = True - SENT_BY_SERVER = False - - MINIMUM_SIZE = 0 # arguments part can never be shorter than this + 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 @@ -3992,10 +3091,10 @@ class TxSelect(AMQPMethodPayload): Create frame tx.select """ - # not generating write_arguments - this method has static content! @staticmethod def from_buffer(buf, start_offset): + offset = start_offset return TxSelect() @@ -4006,26 +3105,13 @@ class TxSelectOk(AMQPMethodPayload): This method confirms to the client that the channel was successfully set to use standard transactions. """ - CLASS = Tx - NAME = u'select-ok' - CLASSNAME = u'tx' - FULLNAME = u'tx.select-ok' + NAME = u'tx.select-ok' - CONTENT_PROPERTY_LIST = TxContentPropertyList - - CLASS_INDEX = 90 - CLASS_INDEX_BINARY = b'\x5A' - METHOD_INDEX = 11 - METHOD_INDEX_BINARY = b'\x0B' + INDEX = (90, 11) # (Class ID, Method ID) BINARY_HEADER = b'\x5A\x0B' # CLASS ID + METHOD ID - SYNCHRONOUS = True # does this message imply other one? - REPLY_WITH = [] - - SENT_BY_CLIENT = False - SENT_BY_SERVER = True - - MINIMUM_SIZE = 0 # arguments part can never be shorter than this + 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 @@ -4037,10 +3123,10 @@ class TxSelectOk(AMQPMethodPayload): Create frame tx.select-ok """ - # not generating write_arguments - this method has static content! @staticmethod def from_buffer(buf, start_offset): + offset = start_offset return TxSelectOk() @@ -4157,3 +3243,8 @@ BINARY_HEADER_TO_METHOD = { b'\x32\x0A': QueueDeclare, } + +CLASS_ID_TO_CONTENT_PROPERTY_LIST = { + 60: BasicContentPropertyList, +} + diff --git a/coolamqp/uplink/frames/field_table.py b/coolamqp/framing/field_table.py similarity index 100% rename from coolamqp/uplink/frames/field_table.py rename to coolamqp/framing/field_table.py diff --git a/coolamqp/uplink/frames/frames.py b/coolamqp/framing/frames.py similarity index 87% rename from coolamqp/uplink/frames/frames.py rename to coolamqp/framing/frames.py index 84a74c8e287917dc43c78d8956a8bb3567e8f529..dad0c7dc06d39dd105dc65135a7c7a2fd10740d5 100644 --- a/coolamqp/uplink/frames/frames.py +++ b/coolamqp/framing/frames.py @@ -6,9 +6,9 @@ from __future__ import absolute_import, division, print_function import struct -from coolamqp.uplink.frames.base import AMQPFrame -from coolamqp.uplink.frames.definitions import FRAME_METHOD, FRAME_HEARTBEAT, FRAME_BODY, FRAME_HEADER, FRAME_END, \ - IDENT_TO_METHOD +from coolamqp.framing.base import AMQPFrame +from coolamqp.framing.definitions import FRAME_METHOD, FRAME_HEARTBEAT, FRAME_BODY, FRAME_HEADER, FRAME_END, \ + IDENT_TO_METHOD, CLASS_ID_TO_CONTENT_PROPERTY_LIST class AMQPMethodFrame(AMQPFrame): @@ -48,10 +48,10 @@ class AMQPHeaderFrame(AMQPFrame): def __init__(self, channel, class_id, weight, body_size, property_flags, property_list): """ - :param channel: - :param class_id: - :param weight: - :param body_size: + :param channel: channel ID + :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: """ diff --git a/coolamqp/scaffold.py b/coolamqp/scaffold.py new file mode 100644 index 0000000000000000000000000000000000000000..7c3d245ce3bd458d561d5ac8d75c8e41df97daf2 --- /dev/null +++ b/coolamqp/scaffold.py @@ -0,0 +1,43 @@ +# coding=UTF-8 +""" +Utilities for building the CoolAMQP engine +""" +from __future__ import absolute_import, division, print_function +import functools +import threading + + +class Synchronized(object): + """Protects access to methods with a lock""" + def __init__(self): + self.lock = threading.Lock() + + def protect(self): + """Make the function body lock-protected""" + def outer(fun): + @functools.wraps(fun) + def inner(*args, **kwargs): + with self.lock: + return fun(*args, **kwargs) + return inner + return outer + + +class AcceptsFrames(object): + """Base class for objects that accept AMQP frames""" + + def on_frame(self, amqp_frame): + """ + :type amqp_frame: AMQPFrame object + """ + + +class EmitsFrames(object): + """Base class for objects that send AMQP frames somewhere""" + + def wire_frame_to(self, acceptor): + """ + Configure this object to send frames somewhere. + + :param acceptor: an AcceptsFrames instance or callable(AMQPMethod instance) + """ \ No newline at end of file diff --git a/coolamqp/uplink/__init__.py b/coolamqp/uplink/__init__.py index ff76c6b8b4f80cd2e56490131288285c23c60957..3fb71d57d79035353c90e1032c51e6df7f8d059a 100644 --- a/coolamqp/uplink/__init__.py +++ b/coolamqp/uplink/__init__.py @@ -1,7 +1,7 @@ # coding=UTF-8 """ The layer that: - - manages serialization/deserializtion (frames) + - manages serialization/deserializtion (framing) - manages low-level data sending (streams) - sets up connection to AMQP - reacts and allows sending low-level AMQP commands diff --git a/coolamqp/uplink/commander/__init__.py b/coolamqp/uplink/commander/__init__.py deleted file mode 100644 index 129d19e03c828885baf8db66a450977d7282dceb..0000000000000000000000000000000000000000 --- a/coolamqp/uplink/commander/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# coding=UTF-8 -""" -Classes that control what Uplink does -""" -from __future__ import absolute_import, division, print_function - - -class BaseCommander(object): - """ - - - This is invoked by user - """ \ No newline at end of file diff --git a/coolamqp/uplink/factory.py b/coolamqp/uplink/factory.py deleted file mode 100644 index 337659bee19e11fbcc2e0d1b9059cadd6ea1511a..0000000000000000000000000000000000000000 --- a/coolamqp/uplink/factory.py +++ /dev/null @@ -1,44 +0,0 @@ -# coding=UTF-8 -""" -Set of objects and functions whose job is to construct an Uplink -instance capable of further action and bootstrap. -""" -from __future__ import absolute_import, division, print_function - -from coolamqp.uplink.frames.base import AMQP_HELLO_HEADER - -import socket - - -def connect(host, port, connect_timeout=10, - general_timeout=5): - """ - Return a TCP socket connected to broker. - - Socket should be in the state of 'Awaiting Connection.Start' - - This may block for up to connect_timeout seconds. - - When returned, this socket will be in the state of awaiting - an - - :param host: host to connect to - :type host: text - :param port: port to connect to - :type port: int - :return: a CONNECTED socket or None, if connection failed. - """ - - try: - s = socket(socket.AF_INET, socket.SOCK_STREAM) - s.settimeout(connect_timeout) - s.setsockopt(socket.SOL_SOCKET, socket.TCP_NODELAY, 1) - s.connect((host, port)) - s.send(AMQP_HELLO_HEADER) - s.settimeout(0) - except (IOError, socket.error, socket.timeout): - try: - s.close() - except: - pass - return None diff --git a/coolamqp/uplink/frames/base_definitions.py b/coolamqp/uplink/frames/base_definitions.py deleted file mode 100644 index befb6426c3df00a2dd538377a5b9efd89fca3263..0000000000000000000000000000000000000000 --- a/coolamqp/uplink/frames/base_definitions.py +++ /dev/null @@ -1,75 +0,0 @@ -# coding=UTF-8 -""" -Used for definitions -""" -from __future__ import absolute_import, division, print_function - -import struct - -from coolamqp.uplink.frames.base import AMQPPayload - - - -class AMQPClass(object): - """An AMQP class""" - - -class AMQPContentPropertyList(object): - """ - A class is intmately bound with content and content properties - """ - PROPERTIES = [] - - -class AMQPMethodPayload(AMQPPayload): - RESPONSE_TO = None - REPLY_WITH = [] - FIELDS = [] - - def write_to(self, buf): - """ - Write own content to target buffer - starting from LENGTH, ending on FRAME_END - :param buf: target buffer - """ - from coolamqp.uplink.frames.definitions import FRAME_END - - if self.IS_CONTENT_STATIC: - buf.write(self.STATIC_CONTENT) - else: - buf.write(struct.pack('!I', self.get_size()+2)) - buf.write(self.BINARY_HEADER) - self.write_arguments(buf) - buf.write(chr(FRAME_END)) - - def get_size(self): - """ - Calculate the size of this frame. - - :return: int, size of argument section - """ - raise NotImplementedError() - - def write_arguments(self, buf): - """ - Write the argument portion of this frame into buffer. - - :param buf: buffer to write to - :return: how many bytes written - :raise ValueError: some field here is invalid! - """ - raise NotImplementedError() - - @staticmethod - def from_buffer(buf, offset): - """ - Construct this frame from a buffer - - :param buf: a buffer to construct the frame from - :type buf: buffer or memoryview - :param offset: offset the argument portion begins at - :type offset: int - :return: tuple of (an instance of %s, amount of bytes consumed as int) - :raise ValueError: invalid data - """ - raise NotImplementedError('') - diff --git a/coolamqp/uplink/frames/compilation/compile_definitions.py b/coolamqp/uplink/frames/compilation/compile_definitions.py deleted file mode 100644 index 56ec91b6e5e60a1d3f693a422c82f086035e7359..0000000000000000000000000000000000000000 --- a/coolamqp/uplink/frames/compilation/compile_definitions.py +++ /dev/null @@ -1,816 +0,0 @@ -from __future__ import division -from xml.etree import ElementTree -import collections -import struct -import six -import math - -from coolamqp.uplink.frames.compilation.utilities import get_constants, get_classes, get_domains, \ - byname, name_class, name_method, name_field, ffmt, doxify, infertype, normname, as_nice_escaped_string, \ - frepr -from coolamqp.uplink.frames.base import BASIC_TYPES - -TYPE_TRANSLATOR = { - 'shortstr': 'binary type (max length 255)', - 'longstr': 'binary type', - 'table': 'table. See coolamqp.uplink.frames.field_table', - 'bit': 'bool', - 'octet': 'int, 8 bit unsigned', - 'short': 'int, 16 bit unsigned', - 'long': 'int, 32 bit unsigned', - 'longlong': 'int, 64 bit unsigned', - 'timestamp': '64 bit signed POSIX timestamp (in seconds)', -} - -def compile_definitions(xml_file='resources/amqp0-9-1.xml', out_file='coolamqp/uplink/frames/definitions.py'): - """parse resources/amqp-0-9-1.xml into """ - - xml = ElementTree.parse(xml_file) - out = open(out_file, 'wb') - - out.write('''# coding=UTF-8 -from __future__ import print_function, absolute_import -""" -A Python version of the AMQP machine-readable specification. - -Generated automatically by CoolAMQP from AMQP machine-readable specification. -See coolamqp.uplink.frames.compilation for the tool - -AMQP is copyright (c) 2016 OASIS -CoolAMQP is copyright (c) 2016 DMS Serwis s.c. -""" - -import struct - -from coolamqp.uplink.frames.base_definitions import AMQPClass, AMQPMethodPayload, AMQPContentPropertyList -from coolamqp.uplink.frames.field_table import enframe_table, deframe_table, frame_table_size - -''') - - def line(data, *args, **kwargs): - out.write(ffmt(data, *args, sane=True)) - - # Output core ones - FRAME_END = None - con_classes = collections.defaultdict(list) - line('# Core constants\n') - for constant in get_constants(xml): - if normname(constant.name) == 'FRAME_END': - FRAME_END = constant.value - g = ffmt('%s = %s', normname(constant.name), constant.value) - line(g) - if constant.docs: - lines = constant.docs.split('\n') - line(' # %s\n', lines[0]) - if len(lines) > 1: - for ln in lines[1:]: - line(u' '*len(g)) - line(u' # %s\n', ln) - else: - line('\n') - - if constant.kind: - con_classes[constant.kind].append(normname(constant.name)) - - for constant_kind, constants in con_classes.items(): - line('\n%s = [%s]', normname(constant_kind), u', '.join(constants)) - - # get domains - domain_to_basic_type = {} - line('\n\n\nDOMAIN_TO_BASIC_TYPE = {\n') - for domain in get_domains(xml): - line(u' %s: %s,\n', frepr(domain.name), frepr(None if domain.elementary else domain.type)) - domain_to_basic_type[domain.name] = domain.type - - line('}\n') - - # Output classes - for cls in get_classes(xml): - - cls = cls._replace(content_properties=[p._replace(basic_type=domain_to_basic_type[p.type]) for p in cls.content_properties]) - - line('''\nclass %s(AMQPClass): - """ - %s - """ - NAME = %s - INDEX = %s - -''', - name_class(cls.name), doxify(None, cls.docs), frepr(cls.name), cls.index) - - line('''\nclass %sContentPropertyList(AMQPContentPropertyList): - """ - %s - """ - CLASS_NAME = %s - CLASS_INDEX = %s - CLASS = %s - - CONTENT_PROPERTIES = [ # tuple of (name, domain, type) - -''', - - name_class(cls.name), doxify(None, cls.docs), frepr(cls.name), cls.index, name_class(cls.name)) - - is_static = all(property.basic_type not in ('table', 'longstr', 'shortstr') for property in cls.content_properties) - - for property in cls.content_properties: - line(' (%s, %s, %s), # %s\n', frepr(property.name), frepr(property.type), frepr(property.basic_type), - frepr(property.label)) - line(' ]\n\n') - - - line(''' def __init__(%s): - """ - Create the property list. - -''', - u', '.join(['self'] + [name_field(property.name) for property in cls.content_properties if not property.reserved]) - ) - - for property in (p for p in cls.content_properties if not p.reserved): - line(''' :param %s: %s - :type %s: %s (%s in AMQP) -''', - name_field(property.name), property.label, name_field(property.name), TYPE_TRANSLATOR[property.basic_type], property.type) - line(''' """ -''') - - for property in (p for p in cls.content_properties if not p.reserved): - - line(' self.%s = %s # %s\n', name_field(property.name), name_field(property.name), property.label) - line('''\n def write_arguments(self, buf): -''') - - def emit_structs(su): - if len(su) == 0: - return - line(" buf.write(struct.pack('!") - line(''.join(a for a, b in su)) - line("', ") - line(', '.join(b for a, b in su)) - line('))\n') - - def emit_bits(bits): - bits = [b for b in bits if b != '0'] # reserved values are out :> - - line(" buf.write(struct.pack('!B', %s))\n", - u' | '.join((u'(int(%s) << %s)' % (bit, position)) for position, bit in enumerate(bits)) - ) - - good_structs = [] - written = False - bits = [] - for property in cls.content_properties: - val = 'self.' + name_field(property.name) if not property.reserved else BASIC_TYPES[property.basic_type][2] - - if (len(bits) == 8) or ((property.basic_type != 'bit') and len(bits) > 0): - emit_bits(bits) - bits = [] - written = True - - if property.basic_type == 'bit': - bits.append(val) - elif property.reserved: - line(" buf.write(" + BASIC_TYPES[property.basic_type][2] + ")\n") - written = True - continue - elif BASIC_TYPES[property.basic_type][1] is None: - # struct can't do it - - if property.basic_type == 'longstr': - good_structs.append(('L', 'len(%s)' % (val,))) - - elif property.basic_type == 'shortstr': - good_structs.append(('B', 'len(%s)' % (val,))) - - emit_structs(good_structs) - good_structs = [] - - if property.basic_type == 'table': - line(' enframe_table(buf, %s)\n' % (val,)) - written = True - else: - # emit ours - line(' buf.write(' + val + ')\n') - written = True - else: - # special case - empty string - if property.basic_type == 'shortstr' and property.reserved: - continue # just skip :) - - val = ('self.' + name_field(property.name)) if not property.reserved else frepr( - BASIC_TYPES[property.basic_type][2], sop=six.binary_type) - - good_structs.append((BASIC_TYPES[property.basic_type][1], val)) - written = True - written = written or len(good_structs) > 0 - emit_structs(good_structs) - if len(bits) > 0: - emit_bits(bits) - written = True - bits = [] - - if not written: - line(' pass # this has a frame, but it''s only default shortstrs\n') - line('\n') - - line(''' @staticmethod - def from_buffer(buf, start_offset): -''') - - if len([f for f in cls.content_properties if not f.reserved]) == 0: - line(" return %sContentPropertyList()\n\n", name_class(cls.name)) - else: - line(""" assert (len(buf) - start_offset) >= %s.MINIMUM_SIZE, 'Content property list too short!' - offset = start_offset # we will use it to count consumed bytes -""", - full_class_name) - # The simple, or the painful way? - has_nonstruct_fields = False - for property in cls.content_properties: - if BASIC_TYPES[property.basic_type][1] is None: - has_nonstruct_fields = True - - if len(cls.content_properties) == 0: - line(' return %s(), 0\n', full_class_name) - elif is_static: - fieldnames = [] - formats = [] - - bits = [] - bit_id = 0 - bits_to_sync_later = {} # bit_0 => [fLSB, fMSB] - - for property in cls.content_properties: - if property.basic_type == 'bit': - bits.append(None if property.reserved else name_field(property.name)) - - if len(bits) == 8: - fieldnames.append('_bit_%s' % (bit_id,)) - formats.append('B') - bits_to_sync_later['_bit_%s' % (bit_id,)] = bits - bits = [] - bit_id += 1 - - elif property.reserved: - formats.append('%sx' % (BASIC_TYPES[property.basic_type][0],)) - else: - fieldnames.append(name_field(property.name)) - formats.append(BASIC_TYPES[property.basic_type][1]) - - # sync bits - if len(bits) > 0: - fieldnames.append('_bit_%s' % (bit_id,)) - formats.append('B') - bits_to_sync_later['_bit_%s' % (bit_id,)] = bits - - line(" %s, = struct.unpack_from('!%s', buf, offset)\n", - u', '.join(fieldnames), - u''.join(formats) - ) - - # If there were any bits, unpack them now - for var_name, bits in bits_to_sync_later.items(): - for bitname, multiplier in zip(bits, (1, 2, 4, 8, 16, 32, 64, 128)): - line(" %s = bool(%s & %s)\n", bitname, var_name, multiplier) - - line(" return %s(%s)", full_class_name, u', '.join([ - name_field(property.name) for field in - cls.content_properties if not property.reserved - ])) - - else: - def emit_bits(bits): - - if all(n == '_' for n in bits): - # everything is reserved, lol - line(""" offset += 1 -""") - return - - line(""" _bit, = struct.unpack_from('!B', buf, offset) - offset += 1 -""") - for bit, multiplier in zip(bits, (1, 2, 4, 8, 16, 32, 64, 128)): - if bit != '_': - line(""" %s = bool(_bit & %s) -""", - bit, multiplier) - - def emit_structures(ss, ln): - line(""" %s, = struct.unpack_from('!%s', buf, offset) - offset += %s -""", - u', '.join([a[0] for a in ss if not (a[0] == '_' and a[1][-1] == 'x')]), - ''.join([a[1] for a in ss]), - ln - ) - - # we'll be counting bytes - to_struct = [] # accumulate static field, (var name, struct_code) - cur_struct_len = 0 # length of current struct - - bits = [] - bit_id = 0 - - for property in cls.content_properties: - fieldname = '_' if property.reserved else name_field(property.name) - - if (len(bits) > 0) and (property.basic_type != 'bit'): - emit_bits(bits) - bits = [] - - # offset is current start - # length is length to read - if BASIC_TYPES[property.basic_type][0] is not None: - if property.reserved: - to_struct.append(('_', '%sx' % (BASIC_TYPES[property.basic_type][0],))) - else: - to_struct.append((fieldname, BASIC_TYPES[property.basic_type][1])) - cur_struct_len += BASIC_TYPES[property.basic_type][0] - elif property.basic_type == 'bit': - bits.append(fieldname) - else: - if property.basic_type == 'table': # oh my god - line(""" %s, delta = deframe_table(buf, offset) - offset += delta -""", name_field(property.name)) - else: # longstr or shortstr - f_q, f_l = ('L', 4) if property.basic_type == 'longstr' else ('B', 1) - to_struct.append(('s_len', f_q)) - cur_struct_len += f_l - emit_structures(to_struct, cur_struct_len) - to_struct, cur_struct_len = [], 0 - if property.reserved: - line(" offset += s_len\n") - else: - line(" %s = buf[offset:offset+s_len]\n offset += s_len\n", - fieldname) - - # check bits for overflow - if len(bits) == 8: - emit_bits(bits) - bits = [] - - if len(bits) > 0: - emit_bits(bits) - elif len(to_struct) > 0: - emit_structures(to_struct, cur_struct_len) - - line(" return %sContentPropertyList(%s)", - name_class(cls.name), - u', '.join(name_field(property.name) for property in cls.content_properties if not property.reserved)) - - line('\n\n') - - line(' def get_size(self):\n return ') - parts = [] - accumulator = 0 - bits = 0 - for property in cls.content_properties: - bt = property.basic_type - - if (bits > 0) and (bt != 'bit'): # sync bits if not - accumulator += int(math.ceil(bits / 8)) - bits = 0 - - if property.basic_type == 'bit': - bits += 1 - elif property.reserved: - accumulator += BASIC_TYPES[property.basic_type][3] - elif BASIC_TYPES[bt][0] is not None: - accumulator += BASIC_TYPES[property.basic_type][0] - elif bt == 'shortstr': - parts.append('len(self.' + name_field(property.name) + ')') - accumulator += 1 - elif bt == 'longstr': - parts.append('len(self.' + name_field(property.name) + ')') - accumulator += 4 - elif bt == 'table': - parts.append('frame_table_size(self.' + name_field(property.name) + ')') - accumulator += 4 - else: - raise Exception() - - if bits > 0: # sync bits - accumulator += int(math.ceil(bits / 8)) - bits = 0 - - parts.append(repr(accumulator)) - line(u' + '.join(parts)) - line('\n\n') - - # ============================================ Do methods for this class - for method in cls.methods: - full_class_name = '%s%s' % (name_class(cls.name), name_method(method.name)) - - # annotate types - method.fields = [field._replace(basic_type=domain_to_basic_type[field.type]) for field in method.fields] - - is_static = method.is_static() - if is_static: - static_size = method.get_size() - - is_content_static = len([f for f in method.fields if not f.reserved]) == 0 - - line('''\nclass %s(AMQPMethodPayload): - """ - %s - """ - CLASS = %s - NAME = %s - CLASSNAME = %s - FULLNAME = %s - - CONTENT_PROPERTY_LIST = %sContentPropertyList - - CLASS_INDEX = %s - CLASS_INDEX_BINARY = %s - METHOD_INDEX = %s - METHOD_INDEX_BINARY = %s - BINARY_HEADER = %s # CLASS ID + METHOD ID - - SYNCHRONOUS = %s # does this message imply other one? - REPLY_WITH = [%s] - - SENT_BY_CLIENT = %s - SENT_BY_SERVER = %s - - MINIMUM_SIZE = %s # arguments part can never be shorter than this - - 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 -''', - full_class_name, - doxify(method.label, method.docs), - name_class(cls.name), - frepr(method.name), - frepr(cls.name), - frepr(cls.name + '.' + method.name), - name_class(cls.name), - frepr(cls.index), - as_nice_escaped_string(chr(cls.index)), - frepr(method.index), - as_nice_escaped_string(chr(method.index)), - as_nice_escaped_string(chr(cls.index)+chr(method.index)), - repr(method.synchronous), - u', '.join([name_class(cls.name)+name_method(kidname) for kidname in method.response]), - repr(method.sent_by_client), - repr(method.sent_by_server), - repr(method.get_minimum_size(domain_to_basic_type)), - repr(is_static), - repr(is_content_static) - ) - - - if is_content_static: - - line(''' STATIC_CONTENT = %s # spans LENGTH, CLASS ID, METHOD ID, ....., FRAME_END -''', - as_nice_escaped_string(struct.pack('!LBB', static_size+4, cls.index, method.index)+\ - 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), name_method(paren.name), - cls.name+'.'+paren.name - ) - - # fields - if len(method.fields) > 0: - line(' FIELDS = [ # tuples of (field name, field domain, basic type used, is_reserved)') - - for field in method.fields: - line('\n (%s, %s, %s, %s), ', frepr(field.name), frepr(field.type), - frepr(field.basic_type), repr(field.reserved)) - if field.label: - line(' # '+field.label) - - line('\n ]\n') - - non_reserved_fields = [field for field in method.fields if not field.reserved] - - # constructor - line('''\n def __init__(%s): - """ - Create frame %s -''', - u', '.join(['self'] + [name_field(field.name) for field in non_reserved_fields]), - cls.name + '.' + method.name, - ) - - 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', name_field(field.name), - doxify(field.label, field.docs, prefix=12, blank=False)) - - - - line(' :type %s: %s (%s in AMQP)\n', name_field(field.name), TYPE_TRANSLATOR[field.basic_type], field.type) - - line(' """\n') - - for field in non_reserved_fields: - line(' self.%s = %s\n', name_field(field.name), name_field(field.name)) - - if len(non_reserved_fields) == 0: - line('\n') - - # end - if not is_content_static: - line('''\n def write_arguments(self, buf): -''') - def emit_structs(su): - if len(su) == 0: - return - line(" buf.write(struct.pack('!") - line(''.join(a for a, b in su)) - line("', ") - line(', '.join(b for a, b in su)) - line('))\n') - - def emit_bits(bits): - bits = [b for b in bits if b != '0'] # reserved values are out :> - - line(" buf.write(struct.pack('!B', %s))\n", - u' | '.join((u'(int(%s) << %s)' % (bit, position)) for position, bit in enumerate(bits)) - ) - - good_structs = [] - written = False - bits = [] - for field in method.fields: - val = 'self.' + name_field(field.name) if not field.reserved else BASIC_TYPES[field.basic_type][2] - - if (len(bits) == 8) or ((field.basic_type != 'bit') and len(bits) > 0): - emit_bits(bits) - bits = [] - written = True - - if field.basic_type == 'bit': - bits.append(val) - elif field.reserved: - line(" buf.write("+BASIC_TYPES[field.basic_type][2]+")\n") - written = True - continue - elif BASIC_TYPES[field.basic_type][1] is None: - # struct can't do it - - if field.basic_type == 'longstr': - good_structs.append(('L', 'len(%s)' % (val, ))) - - elif field.basic_type == 'shortstr': - good_structs.append(('B', 'len(%s)' % (val, ))) - - emit_structs(good_structs) - good_structs = [] - - if field.basic_type == 'table': - line(' enframe_table(buf, %s)\n' % (val, )) - written = True - else: - # emit ours - line(' buf.write('+val+')\n') - written = True - else: - # special case - empty string - if field.basic_type == 'shortstr' and field.reserved: - continue # just skip :) - - val = ('self.'+name_field(field.name)) if not field.reserved else frepr(BASIC_TYPES[field.basic_type][2], sop=six.binary_type) - - good_structs.append((BASIC_TYPES[field.basic_type][1], val)) - written = True - written = written or len(good_structs) > 0 - emit_structs(good_structs) - if len(bits) > 0: - emit_bits(bits) - written = True - bits = [] - - if not written: - line(' pass # this has a frame, but it''s only default shortstrs\n') - line('\n') - - line(' def get_size(self):\n return ') - parts = [] - accumulator = 0 - bits = 0 - for field in method.fields: - bt = field.basic_type - - if (bits > 0) and (bt != 'bit'): # sync bits if not - accumulator += int(math.ceil(bits / 8)) - bits = 0 - - if field.basic_type == 'bit': - bits += 1 - elif field.reserved: - accumulator += BASIC_TYPES[field.basic_type][3] - elif BASIC_TYPES[bt][0] is not None: - accumulator += BASIC_TYPES[field.basic_type][0] - elif bt == 'shortstr': - parts.append('len(self.'+name_field(field.name)+')') - accumulator += 1 - elif bt == 'longstr': - parts.append('len(self.'+name_field(field.name)+')') - accumulator += 4 - elif bt == 'table': - parts.append('frame_table_size(self.'+name_field(field.name)+')') - accumulator += 4 - else: - raise Exception() - - if bits > 0: # sync bits - accumulator += int(math.ceil(bits / 8)) - bits = 0 - - parts.append(repr(accumulator)) - line(u' + '.join(parts)) - line('\n') - else: - line(' # not generating write_arguments - this method has static content!\n') - line('\n') - - line(''' @staticmethod - def from_buffer(buf, start_offset): -''', - full_class_name) - - if is_content_static: - line(" return %s()\n\n", full_class_name) - else: - line(""" assert (len(buf) - start_offset) >= %s.MINIMUM_SIZE, 'Frame too short!' - offset = start_offset # we will use it to count consumed bytes -""", - full_class_name) - # The simple, or the painful way? - has_nonstruct_fields = False - for field in method.fields: - if BASIC_TYPES[field.basic_type][1] is None: - has_nonstruct_fields = True - - if len(method.fields) == 0: - line(' return %s(), 0\n', full_class_name) - elif is_static: - fieldnames = [] - formats = [] - - bits = [] - bit_id = 0 - bits_to_sync_later = {} # bit_0 => [fLSB, fMSB] - - for field in method.fields: - if field.basic_type == 'bit': - bits.append(None if field.reserved else name_field(field.name)) - - if len(bits) == 8: - fieldnames.append('_bit_%s' % (bit_id, )) - formats.append('B') - bits_to_sync_later['_bit_%s' % (bit_id, )] = bits - bits = [] - bit_id += 1 - - elif field.reserved: - formats.append('%sx' % (BASIC_TYPES[field.basic_type][0],)) - else: - fieldnames.append(name_field(field.name)) - formats.append(BASIC_TYPES[field.basic_type][1]) - - # sync bits - if len(bits) > 0: - fieldnames.append('_bit_%s' % (bit_id,)) - formats.append('B') - bits_to_sync_later['_bit_%s' % (bit_id,)] = bits - - line(" %s, = struct.unpack_from('!%s', buf, offset)\n", - u', '.join(fieldnames), - u''.join(formats) - ) - - # If there were any bits, unpack them now - for var_name, bits in bits_to_sync_later.items(): - for bitname, multiplier in zip(bits, (1, 2, 4, 8, 16, 32, 64, 128)): - line(" %s = bool(%s & %s)\n", bitname, var_name, multiplier) - - - line(" return %s(%s)", full_class_name, u', '.join([ - name_field(field.name) for field in method.fields if not field.reserved - ])) - - else: - def emit_bits(bits): - - if all(n == '_' for n in bits): - # everything is reserved, lol - line(""" offset += 1 -""") - return - - line(""" _bit, = struct.unpack_from('!B', buf, offset) - offset += 1 -""") - for bit, multiplier in zip(bits, (1,2,4,8,16,32,64,128)): - if bit != '_': - line(""" %s = bool(_bit & %s) -""", - bit, multiplier) - - def emit_structures(ss, ln): - line(""" %s, = struct.unpack_from('!%s', buf, offset) - offset += %s -""", - u', '.join([a[0] for a in ss if not (a[0] == '_' and a[1][-1] == 'x')]), - ''.join([a[1] for a in ss]), - ln - ) - - # we'll be counting bytes - to_struct = [] # accumulate static field, (var name, struct_code) - cur_struct_len = 0 # length of current struct - - bits = [] - bit_id = 0 - - for field in method.fields: - fieldname = '_' if field.reserved else name_field(field.name) - - if (len(bits) > 0) and (field.basic_type != 'bit'): - emit_bits(bits) - bits = [] - - # offset is current start - # length is length to read - if BASIC_TYPES[field.basic_type][0] is not None: - if field.reserved: - to_struct.append(('_', '%sx' % (BASIC_TYPES[field.basic_type][0],))) - else: - to_struct.append((fieldname, BASIC_TYPES[field.basic_type][1])) - cur_struct_len += BASIC_TYPES[field.basic_type][0] - elif field.basic_type == 'bit': - bits.append(fieldname) - else: - if field.basic_type == 'table': # oh my god - line(""" %s, delta = deframe_table(buf, offset) - offset += delta -""", name_field(field.name)) - else: # longstr or shortstr - f_q, f_l = ('L', 4) if field.basic_type == 'longstr' else ('B', 1) - to_struct.append(('s_len', f_q)) - cur_struct_len += f_l - emit_structures(to_struct, cur_struct_len) - to_struct, cur_struct_len = [], 0 - if field.reserved: - line(" offset += s_len\n") - else: - line(" %s = buf[offset:offset+s_len]\n offset += s_len\n", - fieldname) - - # check bits for overflow - if len(bits) == 8: - emit_bits(bits) - bits = [] - - if len(bits) > 0: - emit_bits(bits) - elif len(to_struct) > 0: - emit_structures(to_struct, cur_struct_len) - - - - line(" return %s(%s)", - full_class_name, - u', '.join(name_field(field.name) for field in method.fields if not field.reserved)) - - - line('\n\n') - - - # Get me a dict - (classid, methodid) => class of method - dct = {} - for cls in get_classes(xml): - for method in cls.methods: - dct[((cls.index, method.index))] = '%s%s' % (name_class(cls.name), name_method(method.name)) - - line('\nIDENT_TO_METHOD = {\n') - for k, v in dct.items(): - line(' %s: %s,\n', repr(k), v) - line('}\n\n') - - line('\nBINARY_HEADER_TO_METHOD = {\n') - for k, v in dct.items(): - line(' %s: %s,\n', as_nice_escaped_string(struct.pack('!BB', *k)), v) - line('}\n\n') - - - out.close() - - -if __name__ == '__main__': - compile_definitions() diff --git a/coolamqp/uplink/iface.py b/coolamqp/uplink/iface.py new file mode 100644 index 0000000000000000000000000000000000000000..9f2b35b38d89264ee25685611d0a65a192e165f6 --- /dev/null +++ b/coolamqp/uplink/iface.py @@ -0,0 +1,2 @@ +# coding=UTF-8 +from __future__ import absolute_import, division, print_function diff --git a/coolamqp/uplink/order.py b/coolamqp/uplink/order.py index 74486d434544705c2cba66781784dd4b04d0dda8..9eaf45cf30914f9f0b86c0b09465d524b1175538 100644 --- a/coolamqp/uplink/order.py +++ b/coolamqp/uplink/order.py @@ -13,7 +13,7 @@ class MethodTransaction(object): class SyncOrder(Order): """ - This means "send a method frame, optionally some other frames, + This means "send a method frame, optionally some other framing, and run a callback with the returned thing. Possible responses are registered for. diff --git a/coolamqp/uplink/streams/reader_thread.py b/coolamqp/uplink/reader_thread.py similarity index 86% rename from coolamqp/uplink/streams/reader_thread.py rename to coolamqp/uplink/reader_thread.py index f5870f34d5811395669926f2ec79486a61bef122..ae333b570769a6ac93a3ef25ca8a18e867143d24 100644 --- a/coolamqp/uplink/streams/reader_thread.py +++ b/coolamqp/uplink/reader_thread.py @@ -6,7 +6,7 @@ import threading class ReaderThread(threading.Thread): """ - A thread, whose job is to receive AMQP frames from AMQP TCP socket. + A thread, whose job is to receive AMQP framing from AMQP TCP socket. Thread may inform Uplink of socket's lossage via on_socket_failed(exception). It should exit afterwards at once. """ @@ -26,7 +26,7 @@ class ReaderThread(threading.Thread): def on_cancel(self): """ - Called by Uplink when it decides that we should not report any more frames, and should just die. + Called by Uplink when it decides that we should not report any more framing, and should just die. """ self.is_cancelled = True diff --git a/coolamqp/uplink/streams/recv_formatter.py b/coolamqp/uplink/recv_framer.py similarity index 89% rename from coolamqp/uplink/streams/recv_formatter.py rename to coolamqp/uplink/recv_framer.py index 528cfcb0790ef68e2c07d24c4e6117c6d3db5462..69e0f07556f7b6f67f22cf987ee25b4c2eecb142 100644 --- a/coolamqp/uplink/streams/recv_formatter.py +++ b/coolamqp/uplink/recv_framer.py @@ -1,15 +1,12 @@ # coding=UTF-8 from __future__ import absolute_import, division, print_function -import struct -import io -import six -import collections -import socket -from coolamqp.uplink.frames.definitions import FRAME_HEADER, FRAME_HEARTBEAT, FRAME_END, FRAME_METHOD, FRAME_BODY -from coolamqp.uplink.frames.frames import AMQPBodyFrame, AMQPHeaderFrame, AMQPHeartbeatFrame, AMQPMethodFrame -from coolamqp.uplink.streams.exceptions import InvalidDataError +import collections +import io +import struct +from coolamqp.framing import AMQPBodyFrame, AMQPHeaderFrame, AMQPHeartbeatFrame, AMQPMethodFrame +from coolamqp.framing import FRAME_HEADER, FRAME_HEARTBEAT, FRAME_END, FRAME_METHOD, FRAME_BODY FRAME_TYPES = { FRAME_HEADER: AMQPHeaderFrame, @@ -20,9 +17,9 @@ FRAME_TYPES = { class ReceivingFormatter(object): """ - Assembles AMQP frames from received data. + Assembles AMQP framing from received data. - Just call with .put(data) and get frames by iterator .frames(). + Just call with .put(data) and get framing by iterator .framing(). Not thread safe. @@ -54,10 +51,10 @@ class ReceivingFormatter(object): def get_frames(self): # -> iterator of AMQPFrame, raises ValueError """ - An iterator to return frames pending for read. + An iterator to return framing pending for read. :raises ValueError: invalid frame readed, kill the connection. - :return: iterator with frames + :return: iterator with framing """ while self._statemachine(): pass diff --git a/coolamqp/uplink/streams/send_operator.py b/coolamqp/uplink/send_framer.py similarity index 94% rename from coolamqp/uplink/streams/send_operator.py rename to coolamqp/uplink/send_framer.py index b76a0f6d57930da25413ab8dab3e15b95c72e2a1..49c88a2953eaf6467f578f6888772f0b0c236055 100644 --- a/coolamqp/uplink/streams/send_operator.py +++ b/coolamqp/uplink/send_framer.py @@ -9,9 +9,9 @@ import socket class SendingOperator(object): """ - Assembles AMQP frames from received data and orchestrates their upload via a socket. + Assembles AMQP framing from received data and orchestrates their upload via a socket. - Just call with .put(data) and get frames by iterator .frames(). + Just call with .put(data) and get framing by iterator .framing(). Not thread safe. @@ -77,7 +77,7 @@ class SendingOperator(object): def send(self, frames, on_done=None, on_fail=None): """ - Schedule to send some frames. + Schedule to send some framing. :param frames: list of AMQPFrame instances :param on_done: callable/0 to call when this is done (frame_end of last frame has just left this PC) :param on_fail: callable(Exception) to call when something broke before the data could be sent. diff --git a/coolamqp/uplink/streams/__init__.py b/coolamqp/uplink/streams/__init__.py deleted file mode 100644 index 772cb445da18001cf14a4324fe9cb776f6b2e966..0000000000000000000000000000000000000000 --- a/coolamqp/uplink/streams/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# coding=UTF-8 -""" -Classes that allow to receive and send frames -REASONABLY FAST, because they use buffers and stuff. -""" -from __future__ import absolute_import, division, print_function - -from coolamqp.uplink.streams.recv_formatter import ReceivingFormatter -from coolamqp.uplink.streams.send_operator import SendingOperator diff --git a/coolamqp/uplink/uplink.py b/coolamqp/uplink/uplink.py deleted file mode 100644 index 905e5f43e028c4477ec65728caa6cd1b4930c1cb..0000000000000000000000000000000000000000 --- a/coolamqp/uplink/uplink.py +++ /dev/null @@ -1,58 +0,0 @@ -# coding=UTF-8 -from __future__ import absolute_import, division, print_function -import collections -from six.moves import queue - - -from coolamqp.uplink.frames.base_definitions import AMQPMethodPayload - - -class Uplink(object): - """ - Uplink, the frame relay manager. - - It coordinates the joint effort of all the classes in this module. - - Uplink brings a thread to life - reader_thread - those job is to recv() on socket. - - - - There are two principal threads that can call Uplink. - 1) CoolAMQP frontend thread: this is the one processing events in the system. This thread - is the only one allowed to send(), and will frequently block on it. - - Well, you can't process more tasks if you are hanging on send, are you. - - 2) Reader thread, spawned and managed by Uplink. This is the thread running recv() on the socket, - calling the callbacks that you register, and so on. - - """ - - def __init__(self): - # Watchers - # Watcher is a one-time trigger set on one (or more) method frames. - self.watchers_to_register = queue.Queue() - pass - - - - def listen_for_method_frame(self, frames, on_frame=lambda f: None, on_dead=None): - """ - Register a one-time listener for a particular method frame (or may kinds of frame). - - When one matching frame arrives, on_frame will be called and rest of subscriptions will be evicted. - - The callback will be executed in Uplink's internal thread context. - - :param frames: - :param on_frame: - :param on_dead: - :return: - """ - - def - - - - def on_socket_failed(self, exc): - """Called by ReaderThread when it detects that socket has failed""" \ No newline at end of file diff --git a/coolamqp/uplink/streams/watchman.py b/coolamqp/uplink/watchman.py similarity index 55% rename from coolamqp/uplink/streams/watchman.py rename to coolamqp/uplink/watchman.py index e244fe45cf8f09f8a2672083b4562958170d34d3..b82bc03bd47eeea613bb9f28ac89159bc054fc68 100644 --- a/coolamqp/uplink/streams/watchman.py +++ b/coolamqp/uplink/watchman.py @@ -4,9 +4,69 @@ import collections from six.moves import queue -class Watchman(object): +from coolamqp.uplink.exceptions import called_by +from coolamqp.scaffold import AcceptsFrames, RelaysFrames, Synchronized + +from coolamqp.framing.frames import AMQPMethodFrame, AMQPHeartbeatFrame, AMQPBodyFrame, AMQPHeaderFrame + + +class Watch(object): + def __init__(self, channel, on_frame, predicate): + """ + :type predicate: callable(AMQPFrame object) -> should_trigger::bool + """ + self.channel = channel + self.on_frame = on_frame + self.predicate = predicate + + + + +class Watchman(AcceptsFrames, RelaysFrames, Synchronized): + """ + Watchman is the guy, who: + + - Receives frames from RecvFramer + - Executes in the context of the Listener + - You can ask to trigger, when a particular frame is received from server """ - Watchman is a guy that you can: + + def __init__(self): + super(Watchman, self).__init__(self) + self.watches = collections.defaultdict(collections.deque) + self.on_frame = lambda frame: None + + def wire_frame_to(self, on_frame): + """ + Set this Watchman to pass non-triggered frames to some callable. + + Called by: Uplink factory + + :param callable: callable(AMQPMethodPayload object) + """ + self.on_frame = on_frame + + @ + def trigger_methods(self, channel, frame_payload_types, on_frame): + """ + Register a one-shot trigger on an AMQP method. + + After triggering upon any of method frame payload types, you'll get a callback with + AMQPMethodPayload instance. This will prevent it from being relayed further. + + Called by: frontend, listener + + :param channel: channel ID + :param frame_payload_types: list of AMQPMethodPayload classes + :param on_frame: callable(AMQPMethodPayload instance) -> n/a + """ + def predicate(frame): + if not isinstance(frame, ) + m = Watch(channel, on_frame, lambda frame: isinstance(frame.payload )) + with self.lock: + + + - If you are ReaderThread, ask "hey, this frame just arrived, is someone interested in it?" @@ -14,7 +74,7 @@ class Watchman(object): when a particular frame arrives (or a bunch of them, if your request expects a bunch). - Since Watchman receives all frames from ReaderThread, it also knows about: + Since Watchman receives all framing from ReaderThread, it also knows about: - channels being opened - channels being closed by exception - @@ -51,7 +111,7 @@ class Watchman(object): def on_frame(self, frame): """ A frame arrived. If this triggers a watch, trigger it and remove. - All frames received by ReaderThread go thru here. + All framing received by ReaderThread go thru here. TO BE CALLED BY READER THREAD diff --git a/setup.py b/setup.py index 03c8c95d3313c5abd6860e31f509865ee9cecfa0..4b387b77bd470ba383663fd152dad00d7bb927f1 100644 --- a/setup.py +++ b/setup.py @@ -14,8 +14,8 @@ setup(name='CoolAMQP', 'coolamqp', 'coolamqp.backends', 'coolamqp.uplink', - 'coolamqp.uplink.frames', - 'coolamqp.uplink.frames.compilation', + 'coolamqp.uplink.framing', + 'coolamqp.uplink.framing.compilation', 'coolamqp.uplink.streams', ],