# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # from __future__ import absolute_import import uuid from cproton import PN_ARRAY, PN_BINARY, PN_BOOL, PN_BYTE, PN_CHAR, PN_DECIMAL128, PN_DECIMAL32, PN_DECIMAL64, \ PN_DESCRIBED, PN_DOUBLE, PN_FLOAT, PN_INT, PN_LIST, PN_LONG, PN_MAP, PN_NULL, PN_OVERFLOW, PN_SHORT, PN_STRING, \ PN_SYMBOL, PN_TIMESTAMP, PN_UBYTE, PN_UINT, PN_ULONG, PN_USHORT, PN_UUID, pn_data, pn_data_clear, pn_data_copy, \ pn_data_decode, pn_data_dump, pn_data_encode, pn_data_encoded_size, pn_data_enter, pn_data_error, pn_data_exit, \ pn_data_format, pn_data_free, pn_data_get_array, pn_data_get_array_type, pn_data_get_binary, pn_data_get_bool, \ pn_data_get_byte, pn_data_get_char, pn_data_get_decimal128, pn_data_get_decimal32, pn_data_get_decimal64, \ pn_data_get_double, pn_data_get_float, pn_data_get_int, pn_data_get_list, pn_data_get_long, pn_data_get_map, \ pn_data_get_short, pn_data_get_string, pn_data_get_symbol, pn_data_get_timestamp, pn_data_get_ubyte, \ pn_data_get_uint, pn_data_get_ulong, pn_data_get_ushort, pn_data_get_uuid, pn_data_is_array_described, \ pn_data_is_described, pn_data_is_null, pn_data_lookup, pn_data_narrow, pn_data_next, pn_data_prev, \ pn_data_put_array, pn_data_put_binary, pn_data_put_bool, pn_data_put_byte, pn_data_put_char, pn_data_put_decimal128, \ pn_data_put_decimal32, pn_data_put_decimal64, pn_data_put_described, pn_data_put_double, pn_data_put_float, \ pn_data_put_int, pn_data_put_list, pn_data_put_long, pn_data_put_map, pn_data_put_null, pn_data_put_short, \ pn_data_put_string, pn_data_put_symbol, pn_data_put_timestamp, pn_data_put_ubyte, pn_data_put_uint, \ pn_data_put_ulong, pn_data_put_ushort, pn_data_put_uuid, pn_data_rewind, pn_data_type, pn_data_widen, pn_error_text from . import _compat from ._common import Constant from ._exceptions import DataException, EXCEPTIONS # # Hacks to provide Python2 <---> Python3 compatibility # # The results are # | |long|unicode| # |Python2|long|unicode| # |Python3| int| str| try: long() except NameError: long = int try: unicode() except NameError: unicode = str class UnmappedType: def __init__(self, msg): self.msg = msg def __repr__(self): return "UnmappedType(%s)" % self.msg class ulong(long): """ The ulong AMQP type. An unsigned 64 bit integer in the range :math:`0` to :math:`2^{64} - 1` inclusive. """ def __init__(self, l): if (l < 0): raise AssertionError("initializing ulong with negative value") super(ulong, self).__new__(ulong, l) def __repr__(self): return "ulong(%s)" % long.__repr__(self) class timestamp(long): """ The timestamp AMQP type. An absolute point in time, represented by a signed 64 bit value measuring milliseconds since the epoch. This value is encoded using the Unix ``time_t`` [IEEE1003] encoding of UTC, but with a precision of milliseconds. For example, ``1311704463521`` represents the moment ``2011-07-26T18:21:03.521Z``. """ def __repr__(self): return "timestamp(%s)" % long.__repr__(self) class symbol(unicode): """ The symbol AMQP type. Symbolic values from a constrained domain, represented by a sequence of ASCII characters. """ def __repr__(self): return "symbol(%s)" % unicode.__repr__(self) class char(unicode): """ The char AMQP type. A 32 bit UTF-32BE encoded Unicode character. """ def __repr__(self): return "char(%s)" % unicode.__repr__(self) class byte(int): """ The byte AMQP type. An 8 bit signed integer in the range :math:`-(2^7)` to :math:`2^7 - 1` inclusive. """ def __repr__(self): return "byte(%s)" % int.__repr__(self) class short(int): """ The short AMQP type. A 16 bit signed integer in the range :math:`-(2^{15})` to :math:`2^{15} - 1` inclusive. """ def __repr__(self): return "short(%s)" % int.__repr__(self) class int32(int): """ The signed int AMQP type. A 32 bit signed integer in the range :math:`-(2^{31})` to :math:`2^{31} - 1` inclusive. """ def __repr__(self): return "int32(%s)" % int.__repr__(self) class ubyte(int): """ The unsigned byte AMQP type. An 8 bit unsigned integer in the range :math:`0` to :math:`2^8 - 1` inclusive. """ def __init__(self, i): if (i < 0): raise AssertionError("initializing ubyte with negative value") super(ubyte, self).__new__(ubyte, i) def __repr__(self): return "ubyte(%s)" % int.__repr__(self) class ushort(int): """ The unsigned short AMQP type. A 16 bit unsigned integer in the range :math:`0` to :math:`2^{16} - 1` inclusive. """ def __init__(self, i): if (i < 0): raise AssertionError("initializing ushort with negative value") super(ushort, self).__new__(ushort, i) def __repr__(self): return "ushort(%s)" % int.__repr__(self) class uint(long): """ The unsigned int AMQP type. A 32 bit unsigned integer in the range :math:`0` to :math:`2^{32} - 1` inclusive. """ def __init__(self, l): if (l < 0): raise AssertionError("initializing uint with negative value") super(uint, self).__new__(uint, l) def __repr__(self): return "uint(%s)" % long.__repr__(self) class float32(float): """ The float AMQP type. A 32 bit floating point number (IEEE 754-2008 binary32). """ def __repr__(self): return "float32(%s)" % float.__repr__(self) class decimal32(int): """ The decimal32 AMQP type. A 32 bit decimal floating point number (IEEE 754-2008 decimal32). """ def __repr__(self): return "decimal32(%s)" % int.__repr__(self) class decimal64(long): """ The decimal64 AMQP type. A 64 bit decimal floating point number (IEEE 754-2008 decimal64). """ def __repr__(self): return "decimal64(%s)" % long.__repr__(self) class decimal128(bytes): """ The decimal128 AMQP type. A 128-bit decimal floating-point number (IEEE 754-2008 decimal128). """ def __repr__(self): return "decimal128(%s)" % bytes.__repr__(self) class Described(object): """ A described AMQP type. :ivar descriptor: A symbol describing the value. :vartype descriptor: :class:`symbol` :ivar value: The described value :vartype value: Any AMQP value """ def __init__(self, descriptor, value): self.descriptor = descriptor self.value = value def __repr__(self): return "Described(%r, %r)" % (self.descriptor, self.value) def __eq__(self, o): if isinstance(o, Described): return self.descriptor == o.descriptor and self.value == o.value else: return False UNDESCRIBED = Constant("UNDESCRIBED") class Array(object): """ An AMQP array, a sequence of AMQP values of a single type. This class provides a convenient way to handle AMQP arrays when used with convenience methods :func:`Data.get_py_array` and :func:`Data.put_py_array`. :ivar descriptor: Optional descriptor if the array is to be described, otherwise ``None`` :ivar type: Array element type, as an integer. The :class:`Data` class has constants defined for all the valid AMQP types. For example, for an array of double values, use :const:`Data.DOUBLE`, which has integer value 14. :ivar elements: A Python list of elements of the appropriate type. """ def __init__(self, descriptor, type, *elements): self.descriptor = descriptor self.type = type self.elements = elements def __iter__(self): return iter(self.elements) def __repr__(self): if self.elements: els = ", %s" % (", ".join(map(repr, self.elements))) else: els = "" return "Array(%r, %r%s)" % (self.descriptor, self.type, els) def __eq__(self, o): if isinstance(o, Array): return self.descriptor == o.descriptor and \ self.type == o.type and self.elements == o.elements else: return False def _check_type(s, allow_ulong=False, raise_on_error=True): if isinstance(s, symbol): return s if allow_ulong and isinstance(s, ulong): return s if isinstance(s, str): # Must be py2 or py3 str return symbol(s) if isinstance(s, unicode): # This must be python2 unicode as we already detected py3 str above return symbol(s.encode('utf-8')) if raise_on_error: raise TypeError('Non-symbol type %s: %s' % (type(s), s)) return s def _check_is_symbol(s, raise_on_error=True): return _check_type(s, allow_ulong=False, raise_on_error=raise_on_error) def _check_is_symbol_or_ulong(s, raise_on_error=True): return _check_type(s, allow_ulong=True, raise_on_error=raise_on_error) class RestrictedKeyDict(dict): """Parent class for :class:`PropertyDict` and :class:`AnnotationDict`""" def __init__(self, validation_fn, e=None, raise_on_error=True, **kwargs): super(RestrictedKeyDict, self).__init__() self.validation_fn = validation_fn self.raise_on_error = raise_on_error self.update(e, **kwargs) def __setitem__(self, key, value): """Checks if the key is a :class:`symbol` type before setting the value""" try: return super(RestrictedKeyDict, self).__setitem__(self.validation_fn(key, self.raise_on_error), value) except TypeError: pass # __setitem__() must raise a KeyError, not TypeError raise KeyError('invalid non-symbol key: %s: %s' % (type(key), key)) def update(self, e=None, **kwargs): """ Equivalent to dict.update(), but it was needed to call :meth:`__setitem__()` instead of ``dict.__setitem__()``. """ if e: try: for k in e: self.__setitem__(k, e[k]) except TypeError: self.__setitem__(k[0], k[1]) # use tuple consumed from from zip for (k, v) in e: self.__setitem__(k, v) for k in kwargs: self.__setitem__(k, kwargs[k]) class PropertyDict(RestrictedKeyDict): """ A dictionary that only takes :class:`symbol` types as a key. However, if a string key is provided, it will be silently converted into a symbol key. >>> from proton import symbol, ulong, PropertyDict >>> a = PropertyDict(one=1, two=2) >>> b = PropertyDict({'one':1, symbol('two'):2}) >>> c = PropertyDict(zip(['one', symbol('two')], [1, 2])) >>> d = PropertyDict([(symbol('one'), 1), ('two', 2)]) >>> e = PropertyDict(a) >>> a == b == c == d == e True By default, non-string and non-symbol keys cause a ``KeyError`` to be raised: >>> PropertyDict({'one':1, 2:'two'}) ... KeyError: "invalid non-symbol key: : 2" but by setting ``raise_on_error=False``, non-string and non-symbol keys will be ignored: >>> PropertyDict({'one':1, 2:'two'}, raise_on_error=False) PropertyDict({2: 'two', symbol(u'one'): 1}) :param e: Initialization for ``dict`` :type e: ``dict`` or ``list`` of ``tuple`` or ``zip`` object :param raise_on_error: If ``True``, will raise an ``KeyError`` if a non-string or non-symbol is encountered as a key in the initialization, or in a subsequent operation which adds such an key. If ``False``, non-strings and non-symbols will be added as keys to the dictionary without an error. :type raise_on_error: ``bool`` :param kwargs: Keyword args for initializing a ``dict`` of the form key1=val1, key2=val2, ... """ def __init__(self, e=None, raise_on_error=True, **kwargs): super(PropertyDict, self).__init__(_check_is_symbol, e, raise_on_error, **kwargs) def __repr__(self): """ Representation of PropertyDict """ return 'PropertyDict(%s)' % super(PropertyDict, self).__repr__() class AnnotationDict(RestrictedKeyDict): """ A dictionary that only takes :class:`symbol` or :class:`ulong` types as a key. However, if a string key is provided, it will be silently converted into a symbol key. >>> from proton import symbol, ulong, AnnotationDict >>> a = AnnotationDict(one=1, two=2) >>> a[ulong(3)] = 'three' >>> b = AnnotationDict({'one':1, symbol('two'):2, ulong(3):'three'}) >>> c = AnnotationDict(zip([symbol('one'), 'two', ulong(3)], [1, 2, 'three'])) >>> d = AnnotationDict([('one', 1), (symbol('two'), 2), (ulong(3), 'three')]) >>> e = AnnotationDict(a) >>> a == b == c == d == e True By default, non-string, non-symbol and non-ulong keys cause a ``KeyError`` to be raised: >>> AnnotationDict({'one': 1, 2: 'two'}) ... KeyError: "invalid non-symbol key: : 2" but by setting ``raise_on_error=False``, non-string, non-symbol and non-ulong keys will be ignored: >>> AnnotationDict({'one': 1, 2: 'two'}, raise_on_error=False) AnnotationDict({2: 'two', symbol(u'one'): 1}) :param e: Initialization for ``dict`` :type e: ``dict`` or ``list`` of ``tuple`` or ``zip`` object :param raise_on_error: If ``True``, will raise an ``KeyError`` if a non-string, non-symbol or non-ulong is encountered as a key in the initialization, or in a subsequent operation which adds such an key. If ``False``, non-strings, non-ulongs and non-symbols will be added as keys to the dictionary without an error. :type raise_on_error: ``bool`` :param kwargs: Keyword args for initializing a ``dict`` of the form key1=val1, key2=val2, ... """ def __init__(self, e=None, raise_on_error=True, **kwargs): super(AnnotationDict, self).__init__(_check_is_symbol_or_ulong, e, raise_on_error, **kwargs) def __repr__(self): """ Representation of AnnotationDict """ return 'AnnotationDict(%s)' % super(AnnotationDict, self).__repr__() class SymbolList(list): """ A list that can only hold :class:`symbol` elements. However, if any string elements are present, they will be converted to symbols. >>> a = SymbolList(['one', symbol('two'), 'three']) >>> b = SymbolList([symbol('one'), 'two', symbol('three')]) >>> c = SymbolList(a) >>> a == b == c True By default, using any key other than a symbol or string will result in a ``TypeError``: >>> SymbolList(['one', symbol('two'), 3]) ... TypeError: Non-symbol type : 3 but by setting ``raise_on_error=False``, non-symbol and non-string keys will be ignored: >>> SymbolList(['one', symbol('two'), 3], raise_on_error=False) SymbolList([symbol(u'one'), symbol(u'two'), 3]) :param t: Initialization for list :type t: ``list`` :param raise_on_error: If ``True``, will raise an ``TypeError`` if a non-string or non-symbol is encountered in the initialization list, or in a subsequent operation which adds such an element. If ``False``, non-strings and non-symbols will be added to the list without an error. :type raise_on_error: ``bool`` """ def __init__(self, t=None, raise_on_error=True): super(SymbolList, self).__init__() self.raise_on_error = raise_on_error if t: self.extend(t) def _check_list(self, t): """ Check all items in list are :class:`symbol`s (or are converted to symbols). """ l = [] if t: for v in t: l.append(_check_is_symbol(v, self.raise_on_error)) return l def append(self, v): """ Add a single value v to the end of the list """ return super(SymbolList, self).append(_check_is_symbol(v, self.raise_on_error)) def extend(self, t): """ Add all elements of an iterable t to the end of the list """ return super(SymbolList, self).extend(self._check_list(t)) def insert(self, i, v): """ Insert a value v at index i """ return super(SymbolList, self).insert(i, _check_is_symbol(v, self.raise_on_error)) def __add__(self, t): """ Handles list1 + list2 """ return SymbolList(super(SymbolList, self).__add__(self._check_list(t)), raise_on_error=self.raise_on_error) def __iadd__(self, t): """ Handles list1 += list2 """ return super(SymbolList, self).__iadd__(self._check_list(t)) def __setitem__(self, i, t): """ Handles list[i] = v """ return super(SymbolList, self).__setitem__(i, _check_is_symbol(t, self.raise_on_error)) def __repr__(self): """ Representation of SymbolList """ return 'SymbolList(%s)' % super(SymbolList, self).__repr__() class Data: """ The :class:`Data` class provides an interface for decoding, extracting, creating, and encoding arbitrary AMQP data. A :class:`Data` object contains a tree of AMQP values. Leaf nodes in this tree correspond to scalars in the AMQP type system such as :const:`ints ` or :const:`strings `. Non-leaf nodes in this tree correspond to compound values in the AMQP type system such as :const:`lists `, :const:`maps `, :const:`arrays `, or :const:`described values `. The root node of the tree is the :class:`Data` object itself and can have an arbitrary number of children. A :class:`Data` object maintains the notion of the current sibling node and a current parent node. Siblings are ordered within their parent. Values are accessed and/or added by using the :meth:`next`, :meth:`prev`, :meth:`enter`, and :meth:`exit` methods to navigate to the desired location in the tree and using the supplied variety of ``put_*`` / ``get_*`` methods to access or add a value of the desired type. The ``put_*`` methods will always add a value *after* the current node in the tree. If the current node has a next sibling the ``put_*`` method will overwrite the value on this node. If there is no current node or the current node has no next sibling then one will be added. The ``put_*`` methods always set the added/modified node to the current node. The ``get_*`` methods read the value of the current node and do not change which node is current. The following types of scalar values are supported: * :const:`NULL` * :const:`BOOL` * :const:`UBYTE` * :const:`USHORT` * :const:`SHORT` * :const:`UINT` * :const:`INT` * :const:`ULONG` * :const:`LONG` * :const:`FLOAT` * :const:`DOUBLE` * :const:`BINARY` * :const:`STRING` * :const:`SYMBOL` The following types of compound values are supported: * :const:`DESCRIBED` * :const:`ARRAY` * :const:`LIST` * :const:`MAP` """ NULL = PN_NULL #: A null value. BOOL = PN_BOOL #: A boolean value. UBYTE = PN_UBYTE #: An unsigned byte value. BYTE = PN_BYTE #: A signed byte value. USHORT = PN_USHORT #: An unsigned short value. SHORT = PN_SHORT #: A short value. UINT = PN_UINT #: An unsigned int value. INT = PN_INT #: A signed int value. CHAR = PN_CHAR #: A character value. ULONG = PN_ULONG #: An unsigned long value. LONG = PN_LONG #: A signed long value. TIMESTAMP = PN_TIMESTAMP #: A timestamp value. FLOAT = PN_FLOAT #: A float value. DOUBLE = PN_DOUBLE #: A double value. DECIMAL32 = PN_DECIMAL32 #: A DECIMAL32 value. DECIMAL64 = PN_DECIMAL64 #: A DECIMAL64 value. DECIMAL128 = PN_DECIMAL128 #: A DECIMAL128 value. UUID = PN_UUID #: A UUID value. BINARY = PN_BINARY #: A binary string. STRING = PN_STRING #: A unicode string. SYMBOL = PN_SYMBOL #: A symbolic string. DESCRIBED = PN_DESCRIBED #: A described value. ARRAY = PN_ARRAY #: An array value. LIST = PN_LIST #: A list value. MAP = PN_MAP #: A map value. type_names = { NULL: "null", BOOL: "bool", BYTE: "byte", UBYTE: "ubyte", SHORT: "short", USHORT: "ushort", INT: "int", UINT: "uint", CHAR: "char", LONG: "long", ULONG: "ulong", TIMESTAMP: "timestamp", FLOAT: "float", DOUBLE: "double", DECIMAL32: "decimal32", DECIMAL64: "decimal64", DECIMAL128: "decimal128", UUID: "uuid", BINARY: "binary", STRING: "string", SYMBOL: "symbol", DESCRIBED: "described", ARRAY: "array", LIST: "list", MAP: "map" } """ A map which uses the enumerated type as a key to get a text name for the type. """ @classmethod def type_name(amqptype): """ Return a string name for an AMQP type. :param type: Numeric Proton AMQP type (`enum pn_type_t`) :type type: integer :rtype: String describing the AMQP type with numeric value `amqptype` """ return Data.type_names[amqptype] def __init__(self, capacity=16): if isinstance(capacity, (int, long)): self._data = pn_data(capacity) self._free = True else: self._data = capacity self._free = False def __del__(self): if self._free and hasattr(self, "_data"): pn_data_free(self._data) del self._data def _check(self, err): if err < 0: exc = EXCEPTIONS.get(err, DataException) raise exc("[%s]: %s" % (err, pn_error_text(pn_data_error(self._data)))) else: return err def clear(self): """ Clears the data object. """ pn_data_clear(self._data) def rewind(self): """ Clears current node and sets the parent to the root node. Clearing the current node sets it _before_ the first node, calling next() will advance to the first node. """ assert self._data is not None pn_data_rewind(self._data) def next(self): """ Advances the current node to its next sibling and returns its type. If there is no next sibling the current node remains unchanged and ``None`` is returned. :return: Node type or ``None`` :rtype: ``int`` or ``None`` """ found = pn_data_next(self._data) if found: return self.type() else: return None def prev(self): """ Advances the current node to its previous sibling and returns its type. If there is no previous sibling the current node remains unchanged and ``None`` is returned. :return: Node type or ``None`` :rtype: ``int`` or ``None`` """ found = pn_data_prev(self._data) if found: return self.type() else: return None def enter(self): """ Sets the parent node to the current node and clears the current node. Clearing the current node sets it *before* the first child, call :meth:`next` to advance to the first child. :return: ``True`` iff the pointers to the current/parent nodes are changed, ``False`` otherwise. :rtype: ``bool`` """ return pn_data_enter(self._data) def exit(self): """ Sets the current node to the parent node and the parent node to its own parent. :return: ``True`` iff the pointers to the current/parent nodes are changed, ``False`` otherwise. :rtype: ``bool`` """ return pn_data_exit(self._data) def lookup(self, name): return pn_data_lookup(self._data, name) def narrow(self): """ Modify this :class:`Data` object to behave as if the current node is the root node of the tree. This impacts the behavior of :meth:`rewind`, :meth:`next`, :meth:`prev`, and anything else that depends on the navigational state of the :class:`Data` object. Use :meth:`widen` to reverse the effect of this operation. """ pn_data_narrow(self._data) def widen(self): """ Reverse the effect of :meth:`narrow`. """ pn_data_widen(self._data) def type(self): """ Returns the type of the current node. Returns `None` if there is no current node. :return: The current node type enumeration. :rtype: ``int`` or ``None`` """ dtype = pn_data_type(self._data) if dtype == -1: return None else: return dtype def encoded_size(self): """ Returns the size in bytes needed to encode the data in AMQP format. :return: The size of the encoded data or an error code if data is invalid. :rtype: ``int`` """ return pn_data_encoded_size(self._data) def encode(self): """ Returns a representation of the data encoded in AMQP format. :return: The size of the encoded data :rtype: ``int`` :raise: :exc:`DataException` if there is a Proton error. """ size = 1024 while True: cd, enc = pn_data_encode(self._data, size) if cd == PN_OVERFLOW: size *= 2 elif cd >= 0: return enc else: self._check(cd) def decode(self, encoded): """ Decodes the first value from supplied AMQP data and returns the number of bytes consumed. :type encoded: binary :param encoded: AMQP encoded binary data :raise: :exc:`DataException` if there is a Proton error. """ return self._check(pn_data_decode(self._data, encoded)) def put_list(self): """ Puts a list value. Elements may be filled by entering the list node and putting element values. >>> data = Data() >>> data.put_list() >>> data.enter() >>> data.put_int(1) >>> data.put_int(2) >>> data.put_int(3) >>> data.exit() :raise: :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_list(self._data)) def put_map(self): """ Puts a map value. Elements may be filled by entering the map node and putting alternating key value pairs. >>> data = Data() >>> data.put_map() >>> data.enter() >>> data.put_string("key") >>> data.put_string("value") >>> data.exit() :raise: :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_map(self._data)) def put_array(self, described, element_type): """ Puts an array value. Elements may be filled by entering the array node and putting the element values. The values must all be of the specified array element type. If an array is described then the first child value of the array is the descriptor and may be of any type. >>> data = Data() >>> >>> data.put_array(False, Data.INT) >>> data.enter() >>> data.put_int(1) >>> data.put_int(2) >>> data.put_int(3) >>> data.exit() >>> >>> data.put_array(True, Data.DOUBLE) >>> data.enter() >>> data.put_symbol("array-descriptor") >>> data.put_double(1.1) >>> data.put_double(1.2) >>> data.put_double(1.3) >>> data.exit() :type described: bool :param described: specifies whether the array is described :type element_type: int :param element_type: the type of the array elements :raise: :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_array(self._data, described, element_type)) def put_described(self): """ Puts a described value. A described node has two children, the descriptor and the value. These are specified by entering the node and putting the desired values. >>> data = Data() >>> data.put_described() >>> data.enter() >>> data.put_symbol("value-descriptor") >>> data.put_string("the value") >>> data.exit() :raise: :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_described(self._data)) def put_null(self): """ Puts a null value. :raise: :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_null(self._data)) def put_bool(self, b): """ Puts a boolean value. :param b: a boolean value :type b: ``bool`` or ``int`` :raise: :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_bool(self._data, b)) def put_ubyte(self, ub): """ Puts an unsigned byte value. :param ub: an integral value in the range :math:`0` to :math:`2^8 - 1` inclusive :type ub: ``int``, :class:`ubyte` :raise: * ``AssertionError`` if parameter is out of the range :math:`0` to :math:`2^8 - 1` inclusive. * :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_ubyte(self._data, ub)) def put_byte(self, b): """ Puts a signed byte value. :param b: an integral value in the range :math:`-(2^7)` to :math:`2^7 - 1` inclusive. :type b: ``int``, :class:`byte` :raise: * ``AssertionError`` if parameter is out of the range :math:`-(2^7)` to :math:`2^7 - 1` inclusive. * :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_byte(self._data, b)) def put_ushort(self, us): """ Puts an unsigned short value. :param us: an integral value in the range :math:`0` to :math:`2^{16} - 1` inclusive. :type us: ``int``, :class:`ushort` :raise: * ``AssertionError`` if parameter is out of the range :math:`0` to :math:`2^{16} - 1` inclusive. * :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_ushort(self._data, us)) def put_short(self, s): """ Puts a signed short value. :param s: an integral value in the range :math:`-(2^{15})` to :math:`2^{15} - 1` inclusive. :type s: ``int``, :class:`short` :raise: * ``AssertionError`` if parameter is out of the range :math:`-(2^{15})` to :math:`2^{15} - 1` inclusive. * :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_short(self._data, s)) def put_uint(self, ui): """ Puts an unsigned int value. :param ui: an integral value in the range :math:`0` to :math:`2^{32} - 1` inclusive. :type ui: ``int``, :class:`uint` :raise: * ``AssertionError`` if parameter is out of the range :math:`0` to :math:`2^{32} - 1` inclusive. * :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_uint(self._data, ui)) def put_int(self, i): """ Puts a signed int value. :param i: an integral value in the range :math:`-(2^{31})` to :math:`2^{31} - 1` inclusive. :type i: ``int``, :class:`int32` :raise: * ``AssertionError`` if parameter is out of the range :math:`-(2^{31})` to :math:`2^{31} - 1` inclusive. * :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_int(self._data, i)) def put_char(self, c): """ Puts a char value. :param c: a single character :type c: ``str``, :class:`char` :raise: :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_char(self._data, ord(c))) def put_ulong(self, ul): """ Puts an unsigned long value. :param ul: an integral value in the range :math:`0` to :math:`2^{64} - 1` inclusive. :type ul: ``int``, ``long``, :class:`ulong` :raise: * ``AssertionError`` if parameter is out of the range :math:`0` to :math:`2^{64} - 1` inclusive. * :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_ulong(self._data, ul)) def put_long(self, l): """ Puts a signed long value. :param l: an integral value in the range :math:`-(2^{63})` to :math:`2^{63} - 1` inclusive. :type ul: ``int``, ``long`` :raise: * ``AssertionError`` if parameter is out of the range :math:`-(2^{63})` to :math:`2^{63} - 1` inclusive. * :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_long(self._data, l)) def put_timestamp(self, t): """ Puts a timestamp value. :param t: a positive integral value :type t: ``int``, :class:`timestamp` :raise: * ``AssertionError`` if parameter is negative. * :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_timestamp(self._data, t)) def put_float(self, f): """ Puts a float value. :param f: a floating point value :type f: ``float``, :class:`float32` :raise: :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_float(self._data, f)) def put_double(self, d): """ Puts a double value. :param d: a floating point value. :type d: ``double`` :raise: :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_double(self._data, d)) def put_decimal32(self, d): """ Puts a decimal32 value. :param d: a decimal32 number encoded in an 32-bit integral value. :type d: ``int``, :class:`decimal32` :raise: :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_decimal32(self._data, d)) def put_decimal64(self, d): """ Puts a decimal64 value. :param d: a decimal64 number encoded in an 32-bit integral value. :type d: ``int``, ``long``, :class:`decimal64` :raise: :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_decimal64(self._data, d)) def put_decimal128(self, d): """ Puts a decimal128 value. :param d: a decimal128 value encoded in a 16-byte binary value. :type d: ``bytes``, :class:`decimal128` :raise: :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_decimal128(self._data, d)) def put_uuid(self, u): """ Puts a UUID value. :param u: a uuid value. :type u: ``uuid.UUID`` :raise: :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_uuid(self._data, u.bytes)) def put_binary(self, b): """ Puts a binary value. :param b: a binary value :type b: ``bytes`` :raise: :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_binary(self._data, b)) def put_memoryview(self, mv): """ Put a Python memoryview object as an AMQP binary value. :param mv: A Python memoryview object :type mv: ``memoryview`` :raise: :exc:`DataException` if there is a Proton error. """ self.put_binary(mv.tobytes()) def put_buffer(self, buff): """ Put a Python buffer object as an AMQP binary value. :param buff: A Python buffer object (**CHECK THIS**) :type buff: Any object supporting the Python buffer interface. :raise: :exc:`DataException` if there is a Proton error. """ self.put_binary(bytes(buff)) def put_string(self, s): """ Puts a unicode value. :param s: a unicode string :type s: ``str`` (Python 3.x) or ``unicode`` (Python 2.x) :raise: :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_string(self._data, s.encode("utf8"))) def put_symbol(self, s): """ Puts a symbolic value. :param s: the symbol name :type s: string, :class:`symbol` :raise: :exc:`DataException` if there is a Proton error. """ self._check(pn_data_put_symbol(self._data, s.encode('ascii'))) def get_list(self): """ If the current node is a list, return the number of elements, otherwise return 0. List elements can be accessed by entering the list. >>> count = data.get_list() >>> data.enter() >>> for i in range(count): ... type = data.next() ... if type == Data.STRING: ... print data.get_string() ... elif type == ...: ... ... >>> data.exit() :return: the number of child elements of a list node :rtype: ``int`` """ return pn_data_get_list(self._data) def get_map(self): """ If the current node is a map, return the number of child elements, otherwise return 0. Key value pairs can be accessed by entering the map. >>> count = data.get_map() >>> data.enter() >>> for i in range(count/2): ... type = data.next() ... if type == Data.STRING: ... print data.get_string() ... elif type == ...: ... ... >>> data.exit() :return: the number of child elements of a map node :rtype: ``int`` """ return pn_data_get_map(self._data) def get_array(self): """ If the current node is an array, return a tuple of the element count, a boolean indicating whether the array is described, and the type of each element, otherwise return ``None``. Array data can be accessed by entering the array. >>> # read an array of strings with a symbolic descriptor >>> count, described, type = data.get_array() >>> data.enter() >>> data.next() >>> print "Descriptor:", data.get_symbol() >>> for i in range(count): ... data.next() ... print "Element:", data.get_string() >>> data.exit() :return: A tuple containing the number of array elements, the descriptor (or ``None`` if no descriptor) and the enumerated array element type. :rtype: ``tuple`` (``int``, ``str`` or ``None``, ``int``) """ count = pn_data_get_array(self._data) described = pn_data_is_array_described(self._data) type = pn_data_get_array_type(self._data) if type == -1: type = None return count, described, type def is_described(self): """ Checks if the current node is a described value. The descriptor and value may be accessed by entering the described value. >>> # read a symbolically described string >>> assert data.is_described() # will error if the current node is not described >>> data.enter() >>> data.next() >>> print data.get_symbol() >>> data.next() >>> print data.get_string() >>> data.exit() :return: ``True`` if the current node is a described type, ``False`` otherwise. :rtype: ``bool`` """ return pn_data_is_described(self._data) def is_null(self): """ Checks if the current node is the AMQP null type. :return: ``True`` if the current node is the AMQP null type, ``False`` otherwise. :rtype: ``bool`` """ return pn_data_is_null(self._data) def get_bool(self): """ Get the current node value as a ``bool``. :return: If the current node is a boolean type, returns its value, ``False`` otherwise. :rtype: ``bool`` """ return pn_data_get_bool(self._data) def get_ubyte(self): """ Get the current node value as a :class:`ubyte`. :return: If the current node is an unsigned byte, its value, 0 otherwise. :rtype: :class:`ubyte` """ return ubyte(pn_data_get_ubyte(self._data)) def get_byte(self): """ Get the current node value as a :class:`byte`. :return: If the current node is a signed byte, its value, 0 otherwise. :rtype: :class:`byte` """ return byte(pn_data_get_byte(self._data)) def get_ushort(self): """ Get the current node value as a :class:`ushort`. :return: If the current node is an unsigned short, its value, 0 otherwise. :rtype: :class:`ushort` """ return ushort(pn_data_get_ushort(self._data)) def get_short(self): """ Get the current node value as a :class:`short`. :return: If the current node is a signed short, its value, 0 otherwise. :rtype: :class:`short` """ return short(pn_data_get_short(self._data)) def get_uint(self): """ Get the current node value as a :class:`uint`. :return: If the current node is an unsigned int, its value, 0 otherwise. :rtype: :class:`uint` """ return uint(pn_data_get_uint(self._data)) def get_int(self): """ Get the current node value as a :class:`int32`. :return: If the current node is a signed int, its value, 0 otherwise. :rtype: :class:`int32` """ return int32(pn_data_get_int(self._data)) def get_char(self): """ Get the current node value as a :class:`char`. :return: If the current node is a char, its value, 0 otherwise. :rtype: :class:`char` """ return char(_compat.unichr(pn_data_get_char(self._data))) def get_ulong(self): """ Get the current node value as a :class:`long`. :return: If the current node is an unsigned long, its value, 0 otherwise. :rtype: :class:`ulong` """ return ulong(pn_data_get_ulong(self._data)) def get_long(self): """ Get the current node value as a :class:`ulong`. :return: If the current node is an signed long, its value, 0 otherwise. :rtype: :class:`long` """ return long(pn_data_get_long(self._data)) def get_timestamp(self): """ Get the current node value as a :class:`timestamp`. :return: If the current node is a timestamp, its value, 0 otherwise. :rtype: :class:`timestamp` """ return timestamp(pn_data_get_timestamp(self._data)) def get_float(self): """ Get the current node value as a :class:`float32`. :return: If the current node is a float, its value, 0 otherwise. :rtype: :class:`float32` """ return float32(pn_data_get_float(self._data)) def get_double(self): """ Get the current node value as a ``double``. :return: If the current node is a double, its value, 0 otherwise. :rtype: ``double`` """ return pn_data_get_double(self._data) # XXX: need to convert def get_decimal32(self): """ Get the current node value as a :class:`decimal32`. :return: If the current node is a decimal32, its value, 0 otherwise. :rtype: :class:`decimal32` """ return decimal32(pn_data_get_decimal32(self._data)) # XXX: need to convert def get_decimal64(self): """ Get the current node value as a :class:`decimal64`. :return: If the current node is a decimal64, its value, 0 otherwise. :rtype: :class:`decimal64` """ return decimal64(pn_data_get_decimal64(self._data)) # XXX: need to convert def get_decimal128(self): """ Get the current node value as a :class:`decimal128`. :return: If the current node is a decimal128, its value, 0 otherwise. :rtype: :class:`decimal128` """ return decimal128(pn_data_get_decimal128(self._data)) def get_uuid(self): """ Get the current node value as a ``uuid.UUID``. :return: If the current node is a UUID, its value, ``None`` otherwise. :rtype: ``uuid.UUID`` or ``None`` """ if pn_data_type(self._data) == Data.UUID: return uuid.UUID(bytes=pn_data_get_uuid(self._data)) else: return None def get_binary(self): """ Get the current node value as ``bytes``. :return: If the current node is binary, its value, ``""`` otherwise. :rtype: ``bytes`` """ return pn_data_get_binary(self._data) def get_string(self): """ Get the current node value as ``str``. :return: If the current node is a string, its value, ``""`` otherwise. :rtype: ``str`` """ return pn_data_get_string(self._data).decode("utf8") def get_symbol(self): """ Get the current node value as :class:`symbol`. :return: If the current node is a symbol, its value, ``""`` otherwise. :rtype: :class:`symbol` """ return symbol(pn_data_get_symbol(self._data).decode('ascii')) def copy(self, src): """ Copy the contents of another pn_data_t object. Any values in the data object will be lost. :param src: The source object from which to copy :type src: :class:`Data` :raise: :exc:`DataException` if there is a Proton error. """ self._check(pn_data_copy(self._data, src._data)) def format(self): """ Formats the contents of this :class:`Data` object in a human readable way. :return: A Formatted string containing contents of this :class:`Data` object. :rtype: ``str`` :raise: :exc:`DataException` if there is a Proton error. """ sz = 16 while True: err, result = pn_data_format(self._data, sz) if err == PN_OVERFLOW: sz *= 2 continue else: self._check(err) return result def dump(self): """ Dumps a debug representation of the internal state of this :class:`Data` object that includes its navigational state to ``cout`` (``stdout``) for debugging purposes. """ pn_data_dump(self._data) def put_dict(self, d): """ A convenience method for encoding the contents of a Python ``dict`` as an AMQP map. :param d: The dictionary to be encoded :type d: ``dict`` :raise: :exc:`DataException` if there is a Proton error. """ self.put_map() self.enter() try: for k, v in d.items(): self.put_object(k) self.put_object(v) finally: self.exit() def get_dict(self): """ A convenience method for decoding an AMQP map as a Python ``dict``. :returns: The decoded dictionary. :rtype: ``dict`` """ if self.enter(): try: result = {} while self.next(): k = self.get_object() if self.next(): v = self.get_object() else: v = None result[k] = v finally: self.exit() return result def put_sequence(self, s): """ A convenience method for encoding a Python ``list`` as an AMQP list. :param s: The sequence to be encoded :type s: ``list`` :raise: :exc:`DataException` if there is a Proton error. """ self.put_list() self.enter() try: for o in s: self.put_object(o) finally: self.exit() def get_sequence(self): """ A convenience method for decoding an AMQP list as a Python ``list``. :returns: The decoded list. :rtype: ``list`` """ if self.enter(): try: result = [] while self.next(): result.append(self.get_object()) finally: self.exit() return result def get_py_described(self): """ A convenience method for decoding an AMQP described value. :returns: The decoded AMQP descriptor. :rtype: :class:`Described` """ if self.enter(): try: self.next() descriptor = self.get_object() self.next() value = self.get_object() finally: self.exit() return Described(descriptor, value) def put_py_described(self, d): """ A convenience method for encoding a :class:`Described` object as an AMQP described value. This method encapsulates all the steps described in :func:`put_described` into a single method. :param d: The descriptor to be encoded :type d: :class:`Described` :raise: :exc:`DataException` if there is a Proton error. """ self.put_described() self.enter() try: self.put_object(d.descriptor) self.put_object(d.value) finally: self.exit() def get_py_array(self): """ A convenience method for decoding an AMQP array into an :class:`Array` object. This method encapsulates all the steps described in :func:`get_array` into a single function. If the current node is an array, return an Array object representing the array and its contents. Otherwise return ``None``. :returns: The decoded AMQP array. :rtype: :class:`Array` """ count, described, type = self.get_array() if type is None: return None if self.enter(): try: if described: self.next() descriptor = self.get_object() else: descriptor = UNDESCRIBED elements = [] while self.next(): elements.append(self.get_object()) finally: self.exit() return Array(descriptor, type, *elements) def put_py_array(self, a): """ A convenience method for encoding an :class:`Array` object as an AMQP array. This method encapsulates the steps described in :func:`put_array` into a single function. :param a: The array object to be encoded :type a: :class:`Array` :raise: :exc:`DataException` if there is a Proton error. """ described = a.descriptor != UNDESCRIBED self.put_array(described, a.type) self.enter() try: if described: self.put_object(a.descriptor) for e in a.elements: self.put_object(e) finally: self.exit() put_mappings = { None.__class__: lambda s, _: s.put_null(), bool: put_bool, ubyte: put_ubyte, ushort: put_ushort, uint: put_uint, ulong: put_ulong, byte: put_byte, short: put_short, int32: put_int, long: put_long, float32: put_float, float: put_double, decimal32: put_decimal32, decimal64: put_decimal64, decimal128: put_decimal128, char: put_char, timestamp: put_timestamp, uuid.UUID: put_uuid, bytes: put_binary, unicode: put_string, symbol: put_symbol, list: put_sequence, tuple: put_sequence, dict: put_dict, Described: put_py_described, Array: put_py_array, AnnotationDict: put_dict, PropertyDict: put_dict, SymbolList: put_sequence } # for Python 3.x, long is merely an alias for int, but for Python 2.x # we need to add an explicit int since it is a different type if int not in put_mappings: put_mappings[int] = put_int # Python >=3.0 has 'memoryview', <=2.5 has 'buffer', >=2.6 has both. try: put_mappings[memoryview] = put_memoryview except NameError: pass try: put_mappings[buffer] = put_buffer except NameError: pass get_mappings = { NULL: lambda s: None, BOOL: get_bool, BYTE: get_byte, UBYTE: get_ubyte, SHORT: get_short, USHORT: get_ushort, INT: get_int, UINT: get_uint, CHAR: get_char, LONG: get_long, ULONG: get_ulong, TIMESTAMP: get_timestamp, FLOAT: get_float, DOUBLE: get_double, DECIMAL32: get_decimal32, DECIMAL64: get_decimal64, DECIMAL128: get_decimal128, UUID: get_uuid, BINARY: get_binary, STRING: get_string, SYMBOL: get_symbol, DESCRIBED: get_py_described, ARRAY: get_py_array, LIST: get_sequence, MAP: get_dict } def put_object(self, obj): putter = self.put_mappings[obj.__class__] putter(self, obj) def get_object(self): type = self.type() if type is None: return None getter = self.get_mappings.get(type) if getter: return getter(self) else: return UnmappedType(str(type)) def dat2obj(dimpl): if dimpl: d = Data(dimpl) d.rewind() d.next() obj = d.get_object() d.rewind() return obj def obj2dat(obj, dimpl): if obj is not None: d = Data(dimpl) d.put_object(obj)