From 51934030922396feeca796863c58f9e43691638f Mon Sep 17 00:00:00 2001 From: Andrew Stitcher Date: Tue, 24 Apr 2018 10:38:57 -0400 Subject: [PATCH 1/2] PROTON-1848: [Python] Remove Python 2.5 and earlier compatibility - Remove need for most compatibility hacks - Fix some seemingly odd conversion functions - Hidden all compatibility code in _compat module - will probably get rid of raise_ when rewriting reactor - can get rid of string_type by doing unicode type hack in reactor.py - leaving iteritems & unichr --- python/proton/__init__.py | 178 +++++++++---------------- python/proton/_compat.py | 57 +++----- python/proton/reactor.py | 20 +-- tests/python/proton_tests/codec.py | 34 +++-- tests/python/proton_tests/engine.py | 68 +++++----- tests/python/proton_tests/interop.py | 5 +- tests/python/proton_tests/message.py | 21 ++- tests/python/proton_tests/sasl.py | 51 ++++--- tests/python/proton_tests/transport.py | 57 ++++---- 9 files changed, 203 insertions(+), 288 deletions(-) diff --git a/python/proton/__init__.py b/python/proton/__init__.py index 60f7323a7a..6ee0d68437 100644 --- a/python/proton/__init__.py +++ b/python/proton/__init__.py @@ -32,94 +32,43 @@ from cproton import * from .wrapper import Wrapper -from proton import _compat - -import logging, weakref, socket, sys, threading - -try: - handler = logging.NullHandler() -except AttributeError: - class NullHandler(logging.Handler): - def handle(self, record): - pass +from . import _compat + +import logging +import socket +import sys +import threading +import uuid +import weakref + +# This private NullHandler is required for Python 2.6, +# when we no longer support 2.6 this replace NullHandler class definition and assignment with: +# handler = logging.NullHandler() +class NullHandler(logging.Handler): + def handle(self, record): + pass - def emit(self, record): - pass + def emit(self, record): + pass - def createLock(self): - self.lock = None + def createLock(self): + self.lock = None - handler = NullHandler() +handler = NullHandler() log = logging.getLogger("proton") log.addHandler(handler) -try: - import uuid - - def generate_uuid(): - return uuid.uuid4() - -except ImportError: - """ - No 'native' UUID support. Provide a very basic UUID type that is a compatible subset of the uuid type provided by more modern python releases. - """ - import struct - class uuid: - class UUID: - def __init__(self, hex=None, bytes=None): - if [hex, bytes].count(None) != 1: - raise TypeError("need one of hex or bytes") - if bytes is not None: - self.bytes = bytes - elif hex is not None: - fields=hex.split("-") - fields[4:5] = [fields[4][:4], fields[4][4:]] - self.bytes = struct.pack("!LHHHHL", *[int(x,16) for x in fields]) - - def __cmp__(self, other): - if isinstance(other, uuid.UUID): - return cmp(self.bytes, other.bytes) - else: - return -1 - - def __str__(self): - return "%08x-%04x-%04x-%04x-%04x%08x" % struct.unpack("!LHHHHL", self.bytes) - - def __repr__(self): - return "UUID(%r)" % str(self) - - def __hash__(self): - return self.bytes.__hash__() - - import os, random, time - rand = random.Random() - rand.seed((os.getpid(), time.time(), socket.gethostname())) - def random_uuid(): - data = [rand.randint(0, 255) for i in xrange(16)] - - # From RFC4122, the version bits are set to 0100 - data[6] &= 0x0F - data[6] |= 0x40 - - # From RFC4122, the top two bits of byte 8 get set to 01 - data[8] &= 0x3F - data[8] |= 0x80 - return "".join(map(chr, data)) - - def uuid4(): - return uuid.UUID(bytes=random_uuid()) - - def generate_uuid(): - return uuid4() +def generate_uuid(): + return uuid.uuid4() # # Hacks to provide Python2 <---> Python3 compatibility # -try: - bytes() -except NameError: - bytes = str +# The results are +# | |long|unicode| +# |python2|long|unicode| +# |python3| int| str| try: long() except NameError: @@ -241,10 +190,15 @@ def _check(self, err): def _check_property_keys(self): for k in self.properties.keys(): - if not isinstance(k, (bytes, str, unicode)): - raise MessageException('Application property key is not unicode string: key=%s %s' % (str(k), type(k))) - if isinstance(k, bytes): - self.properties[_compat.bin2str(k)] = self.properties.pop(k) + if isinstance(k, unicode): + # py2 unicode, py3 str (via hack definition) + continue + # If key is binary then change to string + elif isinstance(k, str): + # py2 str + self.properties[k.encode('utf-8')] = self.properties.pop(k) + else: + raise MessageException('Application property key is not string type: key=%s %s' % (str(k), type(k))) def _pre_encode(self): inst = Data(pn_message_instructions(self._msg)) @@ -376,7 +330,7 @@ def _set_delivery_count(self, value): def _get_id(self): return self._id.get_object() def _set_id(self, value): - if type(value) in _compat.INT_TYPES: + if type(value) in (int, long): value = ulong(value) self._id.rewind() self._id.put_object(value) @@ -432,7 +386,7 @@ def _set_reply_to(self, value): def _get_correlation_id(self): return self._correlation_id.get_object() def _set_correlation_id(self, value): - if type(value) in _compat.INT_TYPES: + if type(value) in (int, long): value = ulong(value) self._correlation_id.rewind() self._correlation_id.put_object(value) @@ -908,7 +862,7 @@ class Data: def type_name(type): return Data.type_names[type] def __init__(self, capacity=16): - if type(capacity) in _compat.INT_TYPES: + if type(capacity) in (int, long): self._data = pn_data(capacity) self._free = True else: @@ -1419,7 +1373,7 @@ def get_char(self): If the current node is a char, returns its value, returns 0 otherwise. """ - return char(_compat.unichar(pn_data_get_char(self._data))) + return char(_compat.unichr(pn_data_get_char(self._data))) def get_ulong(self): """ @@ -1827,37 +1781,33 @@ def millis2timeout(millis): return millis2secs(millis) def unicode2utf8(string): - """Some Proton APIs expect a null terminated string. Convert python text - types to UTF8 to avoid zero bytes introduced by other multi-byte encodings. - This method will throw if the string cannot be converted. - """ - if string is None: - return None - if _compat.IS_PY2: - if isinstance(string, unicode): - return string.encode('utf-8') - elif isinstance(string, str): - return string - else: - # decoding a string results in bytes - if isinstance(string, str): - string = string.encode('utf-8') - # fall through - if isinstance(string, bytes): - return string.decode('utf-8') - raise TypeError("Unrecognized string type: %r (%s)" % (string, type(string))) + """Some Proton APIs expect a null terminated string. Convert python text + types to UTF8 to avoid zero bytes introduced by other multi-byte encodings. + This method will throw if the string cannot be converted. + """ + if string is None: + return None + elif isinstance(string, str): + # Must be py2 or py3 str + # The swig binding converts py3 str -> utf8 char* and back sutomatically + return string + elif isinstance(string, unicode): + # This must be python2 unicode as we already detected py3 str above + return string.encode('utf-8') + # Anything else illegal - specifically python3 bytes + raise TypeError("Unrecognized string type: %r (%s)" % (string, type(string))) def utf82unicode(string): - """Covert C strings returned from proton-c into python unicode""" - if string is None: - return None - if isinstance(string, _compat.TEXT_TYPES): - # already unicode - return string - elif isinstance(string, _compat.BINARY_TYPES): - return string.decode('utf8') - else: - raise TypeError("Unrecognized string type") + """Convert C strings returned from proton-c into python unicode""" + if string is None: + return None + elif isinstance(string, unicode): + # py2 unicode, py3 str (via hack definition) + return string + elif isinstance(string, bytes): + # py2 str (via hack definition), py3 bytes + return string.decode('utf8') + raise TypeError("Unrecognized string type") class Connection(Wrapper, Endpoint): """ diff --git a/python/proton/_compat.py b/python/proton/_compat.py index c8815f456b..afd82e3e6f 100644 --- a/python/proton/_compat.py +++ b/python/proton/_compat.py @@ -22,16 +22,17 @@ """ import sys -import types -IS_PY2 = sys.version_info[0] == 2 -IS_PY3 = sys.version_info[0] == 3 -if IS_PY3: - INT_TYPES = (int,) - TEXT_TYPES = (str,) - STRING_TYPES = (str,) - BINARY_TYPES = (bytes,) - CLASS_TYPES = (type,) +# bridge between py2 Queue renamed as py3 queue +try: + import Queue as queue +except ImportError: + import queue + +PY3 = sys.version_info[0] == 3 + +if PY3: + string_types = (str,) def raise_(t, v=None, tb=None): """Mimic the old 2.x raise behavior: @@ -44,29 +45,13 @@ def raise_(t, v=None, tb=None): else: raise v.with_traceback(tb) - def bin2str(s, encoding='utf-8'): - return s - - def iteritems(d): - return iter(d.items()) - - def unichar(i): - return chr(i) - - def str2bin(s, encoding='latin-1'): - """Convert str to binary type""" - return s.encode(encoding) - - def str2unicode(s): - return s + def iteritems(d, **kw): + return iter(d.items(**kw)) + unichr = chr else: - INT_TYPES = (int, long) - TEXT_TYPES = (unicode,) # includes both unicode and non-unicode strings: - STRING_TYPES = (basestring,) - BINARY_TYPES = (str,) - CLASS_TYPES = (type, types.ClassType) + string_types = (basestring,) # the raise syntax will cause a parse error in Py3, so 'sneak' in a # definition that won't cause the parser to barf @@ -74,17 +59,9 @@ def str2unicode(s): raise t, v, tb """) - def bin2str(s, encoding='utf-8'): - return s.decode(encoding) - def iteritems(d, **kw): - return d.iteritems() - - def unichar(i): - return unichr(i) + return d.iteritems(**kw) - def str2bin(s, encoding='latin-1'): - return s + unichr = unichr - def str2unicode(s): - return unicode(s, "unicode_escape") +__all__ = [ 'PY3', 'queue', 'string_types', 'raise_', 'iteritems', 'unichr'] \ No newline at end of file diff --git a/python/proton/reactor.py b/python/proton/reactor.py index 0fa1c4b65c..d5d5183372 100644 --- a/python/proton/reactor.py +++ b/python/proton/reactor.py @@ -19,6 +19,9 @@ # import logging, os, socket, time, types from heapq import heappush, heappop, nsmallest + +import traceback + from proton import Collector, Connection, ConnectionException, Delivery, Described, dispatch from proton import Endpoint, Event, EventBase, EventType, generate_uuid, Handler, Link, Message from proton import ProtonException, PN_ACCEPTED, PN_PYREF, SASL, Session, SSL, SSLDomain, SSLUnavailable, symbol @@ -27,16 +30,13 @@ from proton.handlers import OutgoingMessageHandler from proton import unicode2utf8, utf82unicode -import traceback from proton import WrappedHandler, _chandler, secs2millis, millis2secs, timeout2millis, millis2timeout, Selectable from .wrapper import Wrapper, PYCTX from cproton import * + from . import _compat -try: - import Queue -except ImportError: - import queue as Queue +from ._compat import queue log = logging.getLogger("proton") @@ -259,7 +259,7 @@ class EventInjector(object): needed, to allow the event loop to end if needed. """ def __init__(self): - self.queue = Queue.Queue() + self.queue = queue.Queue() self.pipe = os.pipe() self._closed = False @@ -269,7 +269,7 @@ def trigger(self, event): of the reactor to which this EventInjector was added. """ self.queue.put(event) - os.write(self.pipe[1], _compat.str2bin("!")) + os.write(self.pipe[1], b"!") def close(self): """ @@ -278,7 +278,7 @@ def close(self): then this will be removed from the set of interest. """ self._closed = True - os.write(self.pipe[1], _compat.str2bin("!")) + os.write(self.pipe[1], b"!") def fileno(self): return self.pipe[0] @@ -806,7 +806,7 @@ def create_sender(self, context, target=None, source=None, name=None, handler=No Various LinkOptions can be specified to further control the attachment. """ - if isinstance(context, _compat.STRING_TYPES): + if isinstance(context, _compat.string_types): context = Url(context) if isinstance(context, Url) and not target: target = context.path @@ -847,7 +847,7 @@ def create_receiver(self, context, source=None, target=None, name=None, dynamic= Various LinkOptions can be specified to further control the attachment. """ - if isinstance(context, _compat.STRING_TYPES): + if isinstance(context, _compat.string_types): context = Url(context) if isinstance(context, Url) and not source: source = context.path diff --git a/tests/python/proton_tests/codec.py b/tests/python/proton_tests/codec.py index 27e70cf14b..e13dcfea9e 100644 --- a/tests/python/proton_tests/codec.py +++ b/tests/python/proton_tests/codec.py @@ -20,11 +20,8 @@ import os, sys from . import common from proton import * -from proton._compat import raise_, str2unicode, unichar, str2bin -try: - from uuid import uuid4 -except ImportError: - from proton import uuid4 +from proton._compat import raise_ +from uuid import uuid4 class Test(common.Test): @@ -281,8 +278,7 @@ def testDouble(self): self._test("double", 0, 1, 2, 3, 0.1, 0.2, 0.3, -1, -2, -3, -0.1, -0.2, -0.3) def testBinary(self): - self._test("binary", str2bin("this"), str2bin("is"), str2bin("a"), str2bin("test"), - str2bin("of" "b\x00inary")) + self._test("binary", b"this", b"is", b"a", b"test",b"of" b"b\x00inary") def testSymbol(self): self._test("symbol", symbol("this is a symbol test"), symbol("bleh"), symbol("blah")) @@ -291,7 +287,7 @@ def testTimestamp(self): self._test("timestamp", timestamp(0), timestamp(12345), timestamp(1000000)) def testChar(self): - self._test("char", char('a'), char('b'), char('c'), char(unichar(0x20AC))) + self._test("char", char('a'), char('b'), char('c'), char(u'\u20AC')) def testUUID(self): self._test("uuid", uuid4(), uuid4(), uuid4()) @@ -303,7 +299,7 @@ def testDecimal64(self): self._test("decimal64", decimal64(0), decimal64(1), decimal64(2), decimal64(3), decimal64(4), decimal64(2**60)) def testDecimal128(self): - self._test("decimal128", decimal128(str2bin("fdsaasdf;lkjjkl;")), decimal128(str2bin("x"*16))) + self._test("decimal128", decimal128(b"fdsaasdf;lkjjkl;"), decimal128(b"x"*16)) def testCopy(self): self.data.put_described() @@ -344,10 +340,10 @@ def testRoundTrip(self): obj = {symbol("key"): timestamp(1234), ulong(123): "blah", char("c"): "bleh", - str2unicode("desc"): Described(symbol("url"), str2unicode("http://example.org")), - str2unicode("array"): Array(UNDESCRIBED, Data.INT, 1, 2, 3), - str2unicode("list"): [1, 2, 3, None, 4], - str2unicode("boolean"): True} + u"desc": Described(symbol("url"), u"http://example.org"), + u"array": Array(UNDESCRIBED, Data.INT, 1, 2, 3), + u"list": [1, 2, 3, None, 4], + u"boolean": True} self.data.put_object(obj) enc = self.data.encode() data = Data() @@ -359,7 +355,7 @@ def testRoundTrip(self): def testBuffer(self): try: - self.data.put_object(buffer(str2bin("foo"))) + self.data.put_object(buffer(b"foo")) except NameError: # python >= 3.0 does not have `buffer` return @@ -368,11 +364,11 @@ def testBuffer(self): data.rewind() assert data.next() assert data.type() == Data.BINARY - assert data.get_object() == str2bin("foo") + assert data.get_object() == b"foo" def testMemoryView(self): try: - self.data.put_object(memoryview(str2bin("foo"))) + self.data.put_object(memoryview(b"foo")) except NameError: # python <= 2.6 does not have `memoryview` return @@ -381,10 +377,10 @@ def testMemoryView(self): data.rewind() assert data.next() assert data.type() == Data.BINARY - assert data.get_object() == str2bin("foo") + assert data.get_object() == b"foo" def testLookup(self): - obj = {symbol("key"): str2unicode("value"), + obj = {symbol("key"): u"value", symbol("pi"): 3.14159, symbol("list"): [1, 2, 3, 4]} self.data.put_object(obj) @@ -396,7 +392,7 @@ def testLookup(self): assert self.data.get_object() == 3.14159 self.data.rewind() assert self.data.lookup("key") - assert self.data.get_object() == str2unicode("value") + assert self.data.get_object() == u"value" self.data.rewind() assert self.data.lookup("list") assert self.data.get_object() == [1, 2, 3, 4] diff --git a/tests/python/proton_tests/engine.py b/tests/python/proton_tests/engine.py index 38a3b4dea5..c0ea31d7ed 100644 --- a/tests/python/proton_tests/engine.py +++ b/tests/python/proton_tests/engine.py @@ -25,7 +25,6 @@ from proton import * from .common import pump, Skipped from proton.reactor import Reactor -from proton._compat import str2bin # older versions of gc do not provide the garbage list @@ -50,7 +49,7 @@ bytearray() except: def bytearray(x): - return str2bin('\x00') * x + return b'\x00' * x OUTPUT_SIZE = 10*1024 @@ -836,7 +835,7 @@ def test_work_queue(self): assert tag == "tag", tag assert d.writable - n = self.snd.send(str2bin("this is a test")) + n = self.snd.send(b"this is a test") assert self.snd.advance() assert self.c1.work_head is None @@ -849,7 +848,7 @@ def test_work_queue(self): def test_multiframe(self): self.rcv.flow(1) self.snd.delivery("tag") - msg = str2bin("this is a test") + msg = b"this is a test" n = self.snd.send(msg) assert n == len(msg) @@ -864,9 +863,9 @@ def test_multiframe(self): assert binary == msg, (binary, msg) binary = self.rcv.recv(1024) - assert binary == str2bin("") + assert binary == b"" - msg = str2bin("this is more") + msg = b"this is more" n = self.snd.send(msg) assert n == len(msg) assert self.snd.advance() @@ -885,7 +884,7 @@ def test_disposition(self): self.pump() sd = self.snd.delivery("tag") - msg = str2bin("this is a test") + msg = b"this is a test" n = self.snd.send(msg) assert n == len(msg) assert self.snd.advance() @@ -984,7 +983,7 @@ def test_cleanup(self): for x in range(10): self.snd.delivery("tag%d" % x) - msg = str2bin("this is a test") + msg = b"this is a test" n = self.snd.send(msg) assert n == len(msg) assert self.snd.advance() @@ -1567,7 +1566,7 @@ def testDrainOrder(self): sd = self.snd.delivery("tagA") assert sd - n = self.snd.send(str2bin("A")) + n = self.snd.send(b"A") assert n == 1 self.pump() self.snd.advance() @@ -1584,7 +1583,7 @@ def testDrainOrder(self): assert self.rcv.credit == 10, self.rcv.credit data = self.rcv.recv(10) - assert data == str2bin("A"), data + assert data == b"A", data self.rcv.advance() self.pump() assert self.snd.credit == 9, self.snd.credit @@ -1605,7 +1604,7 @@ def testDrainOrder(self): sd = self.snd.delivery("tagB") assert sd - n = self.snd.send(str2bin("B")) + n = self.snd.send(b"B") assert n == 1 self.snd.advance() self.pump() @@ -1619,7 +1618,7 @@ def testDrainOrder(self): sd = self.snd.delivery("tagC") assert sd - n = self.snd.send(str2bin("C")) + n = self.snd.send(b"C") assert n == 1 self.snd.advance() self.pump() @@ -1634,10 +1633,10 @@ def testDrainOrder(self): assert self.rcv.credit == 2, self.rcv.credit data = self.rcv.recv(10) - assert data == str2bin("B"), data + assert data == b"B", data self.rcv.advance() data = self.rcv.recv(10) - assert data == str2bin("C"), data + assert data == b"C", data self.rcv.advance() self.pump() assert self.snd.credit == 0, self.snd.credit @@ -1971,8 +1970,9 @@ def test(self): snd.open() for i in range(10): - d = snd.delivery("delivery-%s" % i) - snd.send(str2bin("delivery-%s" % i)) + t = "delivery-%s" % i + d = snd.delivery(t) + snd.send(t.encode('ascii')) d.settle() snd.close() @@ -2402,7 +2402,7 @@ def testDeliveryEvents(self): self.expect(Event.CONNECTION_INIT, Event.SESSION_INIT, Event.LINK_INIT, Event.LINK_LOCAL_OPEN, Event.TRANSPORT) snd.delivery("delivery") - snd.send(str2bin("Hello World!")) + snd.send(b"Hello World!") snd.advance() self.pump() self.expect() @@ -2418,7 +2418,7 @@ def testDeliveryEventsDisp(self): snd, rcv = self.testFlowEvents() snd.open() dlv = snd.delivery("delivery") - snd.send(str2bin("Hello World!")) + snd.send(b"Hello World!") assert snd.advance() self.expect(Event.LINK_LOCAL_OPEN, Event.TRANSPORT) self.pump() @@ -2449,7 +2449,7 @@ def testTransportERROR_CLOSE(self): t.bind(c) self.expect(Event.CONNECTION_BOUND) assert t.condition is None - t.push(str2bin("asdf")) + t.push(b"asdf") self.expect(Event.TRANSPORT_ERROR, Event.TRANSPORT_TAIL_CLOSED) assert t.condition is not None assert t.condition.name == "amqp:connection:framing-error" @@ -2646,9 +2646,9 @@ def testAnonymousNoInitialResponse(self): transport.bind(conn) self.expect(Event.CONNECTION_INIT, Event.CONNECTION_BOUND) - transport.push(str2bin('AMQP\x03\x01\x00\x00\x00\x00\x00 \x02\x01\x00\x00\x00SA' - '\xd0\x00\x00\x00\x10\x00\x00\x00\x02\xa3\tANONYMOUS@' - 'AMQP\x00\x01\x00\x00')) + transport.push(b'AMQP\x03\x01\x00\x00\x00\x00\x00 \x02\x01\x00\x00\x00SA' + b'\xd0\x00\x00\x00\x10\x00\x00\x00\x02\xa3\tANONYMOUS@' + b'AMQP\x00\x01\x00\x00') self.expect(Event.TRANSPORT) for i in range(1024): p = transport.pending() @@ -2664,16 +2664,16 @@ def testPipelinedServerReadFirst(self): s.allowed_mechs("ANONYMOUS PLAIN") transport.bind(conn) self.expect(Event.CONNECTION_INIT, Event.CONNECTION_BOUND) - transport.push(str2bin( + transport.push( # SASL - 'AMQP\x03\x01\x00\x00' + b'AMQP\x03\x01\x00\x00' # @sasl-mechanisms(64) [sasl-server-mechanisms=@PN_SYMBOL[:ANONYMOUS]] - '\x00\x00\x00\x1c\x02\x01\x00\x00\x00S@\xc0\x0f\x01\xe0\x0c\x01\xa3\tANONYMOUS' + b'\x00\x00\x00\x1c\x02\x01\x00\x00\x00S@\xc0\x0f\x01\xe0\x0c\x01\xa3\tANONYMOUS' # @sasl-outcome(68) [code=0] - '\x00\x00\x00\x10\x02\x01\x00\x00\x00SD\xc0\x03\x01P\x00' + b'\x00\x00\x00\x10\x02\x01\x00\x00\x00SD\xc0\x03\x01P\x00' # AMQP - 'AMQP\x00\x01\x00\x00' - )) + b'AMQP\x00\x01\x00\x00' + ) self.expect(Event.TRANSPORT) p = transport.pending() bytes = transport.peek(p) @@ -2695,16 +2695,16 @@ def testPipelinedServerWriteFirst(self): bytes = transport.peek(p) transport.pop(p) self.expect(Event.CONNECTION_INIT, Event.CONNECTION_BOUND) - transport.push(str2bin( + transport.push( # SASL - 'AMQP\x03\x01\x00\x00' + b'AMQP\x03\x01\x00\x00' # @sasl-mechanisms(64) [sasl-server-mechanisms=@PN_SYMBOL[:ANONYMOUS]] - '\x00\x00\x00\x1c\x02\x01\x00\x00\x00S@\xc0\x0f\x01\xe0\x0c\x01\xa3\tANONYMOUS' + b'\x00\x00\x00\x1c\x02\x01\x00\x00\x00S@\xc0\x0f\x01\xe0\x0c\x01\xa3\tANONYMOUS' # @sasl-outcome(68) [code=0] - '\x00\x00\x00\x10\x02\x01\x00\x00\x00SD\xc0\x03\x01P\x00' + b'\x00\x00\x00\x10\x02\x01\x00\x00\x00SD\xc0\x03\x01P\x00' # AMQP - 'AMQP\x00\x01\x00\x00' - )) + b'AMQP\x00\x01\x00\x00' + ) self.expect(Event.TRANSPORT) p = transport.pending() bytes = transport.peek(p) diff --git a/tests/python/proton_tests/interop.py b/tests/python/proton_tests/interop.py index b330f22f84..2825fe4f08 100644 --- a/tests/python/proton_tests/interop.py +++ b/tests/python/proton_tests/interop.py @@ -20,7 +20,6 @@ from proton import * import os from . import common -from proton._compat import str2bin def find_test_interop_dir(): @@ -102,10 +101,10 @@ def test_primitives(self): def test_strings(self): self.decode_data_file("strings") - self.assert_next(Data.BINARY, str2bin("abc\0defg")) + self.assert_next(Data.BINARY, b"abc\0defg") self.assert_next(Data.STRING, "abcdefg") self.assert_next(Data.SYMBOL, "abcdefg") - self.assert_next(Data.BINARY, str2bin("")) + self.assert_next(Data.BINARY, b"") self.assert_next(Data.STRING, "") self.assert_next(Data.SYMBOL, "") assert self.data.next() is None diff --git a/tests/python/proton_tests/message.py b/tests/python/proton_tests/message.py index 199f9325cb..05a067c277 100644 --- a/tests/python/proton_tests/message.py +++ b/tests/python/proton_tests/message.py @@ -20,11 +20,7 @@ import os from . import common from proton import * -from proton._compat import str2bin -try: - from uuid import uuid4 -except ImportError: - from proton import uuid4 +from uuid import uuid4 class Test(common.Test): @@ -76,8 +72,7 @@ def testDeliveryCount(self): self._test("delivery_count", 0, range(0, 1024)) def testUserId(self): - self._test("user_id", str2bin(""), (str2bin("asdf"), str2bin("fdsa"), - str2bin("asd\x00fdsa"), str2bin(""))) + self._test("user_id", b"", (b"asdf", b"fdsa", b"asd\x00fdsa", b"")) def testAddress(self): self._test_str("address") @@ -214,21 +209,21 @@ def testGroupSequenceEncodeAsNonNull(self): def testDefaultCreationExpiryDecode(self): # This is a message with everything filled explicitly as null or zero in LIST32 HEADER and PROPERTIES lists - data = str2bin('\x00\x53\x70\xd0\x00\x00\x00\x0a\x00\x00\x00\x05\x42\x40\x40\x42\x52\x00\x00\x53\x73\xd0\x00\x00\x00\x12\x00\x00\x00\x0d\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x52\x00\x40') + data = b'\x00\x53\x70\xd0\x00\x00\x00\x0a\x00\x00\x00\x05\x42\x40\x40\x42\x52\x00\x00\x53\x73\xd0\x00\x00\x00\x12\x00\x00\x00\x0d\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x52\x00\x40' msg2 = Message() msg2.decode(data) assert msg2.expiry_time == 0, (msg2.expiry_time) assert msg2.creation_time == 0, (msg2.creation_time) # The same message with LIST8s instead - data = str2bin('\x00\x53\x70\xc0\x07\x05\x42\x40\x40\x42\x52\x00\x00\x53\x73\xc0\x0f\x0d\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x52\x00\x40') + data = b'\x00\x53\x70\xc0\x07\x05\x42\x40\x40\x42\x52\x00\x00\x53\x73\xc0\x0f\x0d\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x52\x00\x40' msg3 = Message() msg3.decode(data) assert msg2.expiry_time == 0, (msg2.expiry_time) assert msg2.creation_time == 0, (msg2.creation_time) # Minified message with zero length HEADER and PROPERTIES lists - data = str2bin('\x00\x53\x70\x45' '\x00\x53\x73\x45') + data = b'\x00\x53\x70\x45' b'\x00\x53\x73\x45' msg4 = Message() msg4.decode(data) assert msg2.expiry_time == 0, (msg2.expiry_time) @@ -252,19 +247,19 @@ def testDefaultPriorityEncode(self): def testDefaultPriorityDecode(self): # This is a message with everything filled explicitly as null or zero in LIST32 HEADER and PROPERTIES lists - data = str2bin('\x00\x53\x70\xd0\x00\x00\x00\x0a\x00\x00\x00\x05\x42\x40\x40\x42\x52\x00\x00\x53\x73\xd0\x00\x00\x00\x22\x00\x00\x00\x0d\x40\x40\x40\x40\x40\x40\x40\x40\x83\x00\x00\x00\x00\x00\x00\x00\x00\x83\x00\x00\x00\x00\x00\x00\x00\x00\x40\x52\x00\x40') + data = b'\x00\x53\x70\xd0\x00\x00\x00\x0a\x00\x00\x00\x05\x42\x40\x40\x42\x52\x00\x00\x53\x73\xd0\x00\x00\x00\x22\x00\x00\x00\x0d\x40\x40\x40\x40\x40\x40\x40\x40\x83\x00\x00\x00\x00\x00\x00\x00\x00\x83\x00\x00\x00\x00\x00\x00\x00\x00\x40\x52\x00\x40' msg2 = Message() msg2.decode(data) assert msg2.priority == 4, (msg2.priority) # The same message with LIST8s instead - data = str2bin('\x00\x53\x70\xc0\x07\x05\x42\x40\x40\x42\x52\x00\x00\x53\x73\xc0\x1f\x0d\x40\x40\x40\x40\x40\x40\x40\x40\x83\x00\x00\x00\x00\x00\x00\x00\x00\x83\x00\x00\x00\x00\x00\x00\x00\x00\x40\x52\x00\x40') + data = b'\x00\x53\x70\xc0\x07\x05\x42\x40\x40\x42\x52\x00\x00\x53\x73\xc0\x1f\x0d\x40\x40\x40\x40\x40\x40\x40\x40\x83\x00\x00\x00\x00\x00\x00\x00\x00\x83\x00\x00\x00\x00\x00\x00\x00\x00\x40\x52\x00\x40' msg3 = Message() msg3.decode(data) assert msg3.priority == 4, (msg3.priority) # Minified message with zero length HEADER and PROPERTIES lists - data = str2bin('\x00\x53\x70\x45' '\x00\x53\x73\x45') + data = b'\x00\x53\x70\x45' b'\x00\x53\x73\x45' msg4 = Message() msg4.decode(data) assert msg4.priority == 4, (msg4.priority) diff --git a/tests/python/proton_tests/sasl.py b/tests/python/proton_tests/sasl.py index 804c828e13..68ae200b9c 100644 --- a/tests/python/proton_tests/sasl.py +++ b/tests/python/proton_tests/sasl.py @@ -24,7 +24,6 @@ from proton import * from .common import pump, Skipped -from proton._compat import str2bin def _sslCertpath(file): """ Return the full path to the certificate, keyfile, etc. @@ -113,20 +112,20 @@ def testIllegalProtocolLayering(self): assert self.s2.outcome is None # Push client bytes into server - self.t2.push(str2bin( + self.t2.push( # SASL - 'AMQP\x03\x01\x00\x00' + b'AMQP\x03\x01\x00\x00' # @sasl-init(65) [mechanism=:ANONYMOUS, initial-response=b"anonymous@fuschia"] - '\x00\x00\x002\x02\x01\x00\x00\x00SA\xd0\x00\x00\x00"\x00\x00\x00\x02\xa3\x09ANONYMOUS\xa0\x11anonymous@fuschia' + b'\x00\x00\x002\x02\x01\x00\x00\x00SA\xd0\x00\x00\x00"\x00\x00\x00\x02\xa3\x09ANONYMOUS\xa0\x11anonymous@fuschia' # SASL (again illegally) - 'AMQP\x03\x01\x00\x00' + b'AMQP\x03\x01\x00\x00' # @sasl-init(65) [mechanism=:ANONYMOUS, initial-response=b"anonymous@fuschia"] - '\x00\x00\x002\x02\x01\x00\x00\x00SA\xd0\x00\x00\x00"\x00\x00\x00\x02\xa3\x09ANONYMOUS\xa0\x11anonymous@fuschia' + b'\x00\x00\x002\x02\x01\x00\x00\x00SA\xd0\x00\x00\x00"\x00\x00\x00\x02\xa3\x09ANONYMOUS\xa0\x11anonymous@fuschia' # AMQP - 'AMQP\x00\x01\x00\x00' + b'AMQP\x00\x01\x00\x00' # @open(16) [container-id="", channel-max=1234] - '\x00\x00\x00!\x02\x00\x00\x00\x00S\x10\xd0\x00\x00\x00\x11\x00\x00\x00\x0a\xa1\x00@@`\x04\xd2@@@@@@' - )) + b'\x00\x00\x00!\x02\x00\x00\x00\x00S\x10\xd0\x00\x00\x00\x11\x00\x00\x00\x0a\xa1\x00@@`\x04\xd2@@@@@@' + ) consumeAllOuput(self.t2) @@ -144,16 +143,16 @@ def testPipelinedClient(self): assert self.s2.outcome is None # Push client bytes into server - self.t2.push(str2bin( + self.t2.push( # SASL - 'AMQP\x03\x01\x00\x00' + b'AMQP\x03\x01\x00\x00' # @sasl-init(65) [mechanism=:ANONYMOUS, initial-response=b"anonymous@fuschia"] - '\x00\x00\x002\x02\x01\x00\x00\x00SA\xd0\x00\x00\x00"\x00\x00\x00\x02\xa3\x09ANONYMOUS\xa0\x11anonymous@fuschia' + b'\x00\x00\x002\x02\x01\x00\x00\x00SA\xd0\x00\x00\x00"\x00\x00\x00\x02\xa3\x09ANONYMOUS\xa0\x11anonymous@fuschia' # AMQP - 'AMQP\x00\x01\x00\x00' + b'AMQP\x00\x01\x00\x00' # @open(16) [container-id="", channel-max=1234] - '\x00\x00\x00!\x02\x00\x00\x00\x00S\x10\xd0\x00\x00\x00\x11\x00\x00\x00\x0a\xa1\x00@@`\x04\xd2@@@@@@' - )) + b'\x00\x00\x00!\x02\x00\x00\x00\x00S\x10\xd0\x00\x00\x00\x11\x00\x00\x00\x0a\xa1\x00@@`\x04\xd2@@@@@@' + ) consumeAllOuput(self.t2) @@ -173,18 +172,18 @@ def testPipelinedServer(self): # Push server bytes into client # Commented out lines in this test are where the client input processing doesn't # run after output processing even though there is input waiting - self.t1.push(str2bin( + self.t1.push( # SASL - 'AMQP\x03\x01\x00\x00' + b'AMQP\x03\x01\x00\x00' # @sasl-mechanisms(64) [sasl-server-mechanisms=@PN_SYMBOL[:ANONYMOUS]] - '\x00\x00\x00\x1c\x02\x01\x00\x00\x00S@\xc0\x0f\x01\xe0\x0c\x01\xa3\tANONYMOUS' + b'\x00\x00\x00\x1c\x02\x01\x00\x00\x00S@\xc0\x0f\x01\xe0\x0c\x01\xa3\tANONYMOUS' # @sasl-outcome(68) [code=0] - '\x00\x00\x00\x10\x02\x01\x00\x00\x00SD\xc0\x03\x01P\x00' + b'\x00\x00\x00\x10\x02\x01\x00\x00\x00SD\xc0\x03\x01P\x00' # AMQP - 'AMQP\x00\x01\x00\x00' + b'AMQP\x00\x01\x00\x00' # @open(16) [container-id="", channel-max=1234] - '\x00\x00\x00!\x02\x00\x00\x00\x00S\x10\xd0\x00\x00\x00\x11\x00\x00\x00\x0a\xa1\x00@@`\x04\xd2@@@@@@' - )) + b'\x00\x00\x00!\x02\x00\x00\x00\x00S\x10\xd0\x00\x00\x00\x11\x00\x00\x00\x0a\xa1\x00@@`\x04\xd2@@@@@@' + ) consumeAllOuput(self.t1) @@ -217,17 +216,17 @@ def testFracturedSASL(self): out = self.t1.peek(1024) self.t1.pop(len(out)) - self.t1.push(str2bin("AMQP\x03\x01\x00\x00")) + self.t1.push(b"AMQP\x03\x01\x00\x00") out = self.t1.peek(1024) self.t1.pop(len(out)) - self.t1.push(str2bin("\x00\x00\x00")) + self.t1.push(b"\x00\x00\x00") out = self.t1.peek(1024) self.t1.pop(len(out)) - self.t1.push(str2bin("6\x02\x01\x00\x00\x00S@\xc0\x29\x01\xe0\x26\x04\xa3\x05PLAIN\x0aDIGEST-MD5\x09ANONYMOUS\x08CRAM-MD5")) + self.t1.push(b"6\x02\x01\x00\x00\x00S@\xc0\x29\x01\xe0\x26\x04\xa3\x05PLAIN\x0aDIGEST-MD5\x09ANONYMOUS\x08CRAM-MD5") out = self.t1.peek(1024) self.t1.pop(len(out)) - self.t1.push(str2bin("\x00\x00\x00\x10\x02\x01\x00\x00\x00SD\xc0\x03\x01P\x00")) + self.t1.push(b"\x00\x00\x00\x10\x02\x01\x00\x00\x00SD\xc0\x03\x01P\x00") out = self.t1.peek(1024) self.t1.pop(len(out)) while out: diff --git a/tests/python/proton_tests/transport.py b/tests/python/proton_tests/transport.py index 5a5cea2319..5390d07be9 100644 --- a/tests/python/proton_tests/transport.py +++ b/tests/python/proton_tests/transport.py @@ -21,7 +21,6 @@ import sys from . import common from proton import * -from proton._compat import str2bin class Test(common.Test): @@ -63,16 +62,16 @@ def assert_error(self, name): assert self.conn.remote_condition.name == name, self.conn.remote_condition def testEOS(self): - self.transport.push(str2bin("")) # should be a noop + self.transport.push(b"") # should be a noop self.transport.close_tail() # should result in framing error self.assert_error(u'amqp:connection:framing-error') def testPartial(self): - self.transport.push(str2bin("AMQ")) # partial header + self.transport.push(b"AMQ") # partial header self.transport.close_tail() # should result in framing error self.assert_error(u'amqp:connection:framing-error') - def testGarbage(self, garbage=str2bin("GARBAGE_")): + def testGarbage(self, garbage=b"GARBAGE_"): self.transport.push(garbage) self.assert_error(u'amqp:connection:framing-error') assert self.transport.pending() < 0 @@ -80,35 +79,35 @@ def testGarbage(self, garbage=str2bin("GARBAGE_")): assert self.transport.pending() < 0 def testSmallGarbage(self): - self.testGarbage(str2bin("XXX")) + self.testGarbage(b"XXX") def testBigGarbage(self): - self.testGarbage(str2bin("GARBAGE_XXX")) + self.testGarbage(b"GARBAGE_XXX") def testHeader(self): - self.transport.push(str2bin("AMQP\x00\x01\x00\x00")) + self.transport.push(b"AMQP\x00\x01\x00\x00") self.transport.close_tail() self.assert_error(u'amqp:connection:framing-error') def testHeaderBadDOFF1(self): """Verify doff > size error""" - self.testGarbage(str2bin("AMQP\x00\x01\x00\x00\x00\x00\x00\x08\x08\x00\x00\x00")) + self.testGarbage(b"AMQP\x00\x01\x00\x00\x00\x00\x00\x08\x08\x00\x00\x00") def testHeaderBadDOFF2(self): """Verify doff < 2 error""" - self.testGarbage(str2bin("AMQP\x00\x01\x00\x00\x00\x00\x00\x08\x01\x00\x00\x00")) + self.testGarbage(b"AMQP\x00\x01\x00\x00\x00\x00\x00\x08\x01\x00\x00\x00") def testHeaderBadSize(self): """Verify size > max_frame_size error""" self.transport.max_frame_size = 512 - self.testGarbage(str2bin("AMQP\x00\x01\x00\x00\x00\x00\x02\x01\x02\x00\x00\x00")) + self.testGarbage(b"AMQP\x00\x01\x00\x00\x00\x00\x02\x01\x02\x00\x00\x00") def testProtocolNotSupported(self): - self.transport.push(str2bin("AMQP\x01\x01\x0a\x00")) + self.transport.push(b"AMQP\x01\x01\x0a\x00") p = self.transport.pending() assert p >= 8, p bytes = self.transport.peek(p) - assert bytes[:8] == str2bin("AMQP\x00\x01\x00\x00") + assert bytes[:8] == b"AMQP\x00\x01\x00\x00" self.transport.pop(p) self.drain() assert self.transport.closed @@ -127,8 +126,8 @@ def testBindAfterOpen(self): trn = Transport() trn.bind(conn) out = trn.peek(1024) - assert str2bin("test-container") in out, repr(out) - assert str2bin("test-hostname") in out, repr(out) + assert b"test-container" in out, repr(out) + assert b"test-hostname" in out, repr(out) self.transport.push(out) c = Connection() @@ -185,7 +184,7 @@ def testUnpairedPop(self): self.transport.pop(len(dat2) - len(dat1)) dat3 = self.transport.peek(1024) self.transport.pop(len(dat3)) - assert self.transport.peek(1024) == str2bin("") + assert self.transport.peek(1024) == b"" self.peer.push(dat1) self.peer.push(dat2[len(dat1):]) @@ -228,50 +227,50 @@ def assert_error(self, name): # TODO: This may no longer be testing anything def testEOS(self): - self.transport.push(str2bin("")) # should be a noop + self.transport.push(b"") # should be a noop self.transport.close_tail() p = self.transport.pending() self.drain() assert self.transport.closed def testPartial(self): - self.transport.push(str2bin("AMQ")) # partial header + self.transport.push(b"AMQ") # partial header self.transport.close_tail() p = self.transport.pending() assert p >= 8, p bytes = self.transport.peek(p) - assert bytes[:8] == str2bin("AMQP\x00\x01\x00\x00") + assert bytes[:8] == b"AMQP\x00\x01\x00\x00" self.transport.pop(p) self.drain() assert self.transport.closed - def testGarbage(self, garbage="GARBAGE_"): - self.transport.push(str2bin(garbage)) + def testGarbage(self, garbage=b"GARBAGE_"): + self.transport.push(garbage) p = self.transport.pending() assert p >= 8, p bytes = self.transport.peek(p) - assert bytes[:8] == str2bin("AMQP\x00\x01\x00\x00") + assert bytes[:8] == b"AMQP\x00\x01\x00\x00" self.transport.pop(p) self.drain() assert self.transport.closed def testSmallGarbage(self): - self.testGarbage("XXX") + self.testGarbage(b"XXX") def testBigGarbage(self): - self.testGarbage("GARBAGE_XXX") + self.testGarbage(b"GARBAGE_XXX") def testHeader(self): - self.transport.push(str2bin("AMQP\x00\x01\x00\x00")) + self.transport.push(b"AMQP\x00\x01\x00\x00") self.transport.close_tail() self.assert_error(u'amqp:connection:framing-error') def testProtocolNotSupported(self): - self.transport.push(str2bin("AMQP\x01\x01\x0a\x00")) + self.transport.push(b"AMQP\x01\x01\x0a\x00") p = self.transport.pending() assert p >= 8, p bytes = self.transport.peek(p) - assert bytes[:8] == str2bin("AMQP\x00\x01\x00\x00") + assert bytes[:8] == b"AMQP\x00\x01\x00\x00" self.transport.pop(p) self.drain() assert self.transport.closed @@ -290,8 +289,8 @@ def testBindAfterOpen(self): trn = Transport() trn.bind(conn) out = trn.peek(1024) - assert str2bin("test-container") in out, repr(out) - assert str2bin("test-hostname") in out, repr(out) + assert b"test-container" in out, repr(out) + assert b"test-hostname" in out, repr(out) self.transport.push(out) c = Connection() @@ -348,7 +347,7 @@ def testUnpairedPop(self): self.transport.pop(len(dat2) - len(dat1)) dat3 = self.transport.peek(1024) self.transport.pop(len(dat3)) - assert self.transport.peek(1024) == str2bin("") + assert self.transport.peek(1024) == b"" self.peer.push(dat1) self.peer.push(dat2[len(dat1):]) From d28fecf5d5388734508fa79e3ec33fe975bd060e Mon Sep 17 00:00:00 2001 From: Andrew Stitcher Date: Wed, 2 May 2018 19:20:38 -0400 Subject: [PATCH 2/2] PROTON-1850: Split up proton __init__.py into multiple files - Reformatted python source to (mostly) PEP-8 standards - Control what gets exported from __init__ by restricting what it imports - Move most of the reactor implementation specific code into _reactor_impl.py --- python/CMakeLists.txt | 17 +- python/proton/__init__.py | 3726 +-------------------- python/proton/_common.py | 91 + python/proton/_compat.py | 13 +- python/proton/_condition.py | 63 + python/proton/_data.py | 1129 +++++++ python/proton/_delivery.py | 293 ++ python/proton/_endpoints.py | 765 +++++ python/proton/_events.py | 333 ++ python/proton/_exceptions.py | 92 + python/proton/_message.py | 465 +++ python/proton/_reactor_impl.py | 217 ++ python/proton/_transport.py | 524 +++ python/proton/_url.py | 161 + python/proton/{wrapper.py => _wrapper.py} | 16 +- python/proton/handlers.py | 66 +- python/proton/reactor.py | 138 +- python/proton/utils.py | 54 +- 18 files changed, 4444 insertions(+), 3719 deletions(-) create mode 100644 python/proton/_common.py create mode 100644 python/proton/_condition.py create mode 100644 python/proton/_data.py create mode 100644 python/proton/_delivery.py create mode 100644 python/proton/_endpoints.py create mode 100644 python/proton/_events.py create mode 100644 python/proton/_exceptions.py create mode 100644 python/proton/_message.py create mode 100644 python/proton/_reactor_impl.py create mode 100644 python/proton/_transport.py create mode 100644 python/proton/_url.py rename python/proton/{wrapper.py => _wrapper.py} (88%) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index bdd1af7265..2c3f4d93d9 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -64,11 +64,24 @@ endif() set (pysrc-generated cproton.py) set (pysrc proton/__init__.py + proton/_compat.py + proton/_common.py + proton/_condition.py + proton/_data.py + proton/_delivery.py + proton/_endpoints.py + proton/_events.py + proton/_exceptions.py + proton/_message.py + proton/_transport.py + proton/_url.py + proton/_wrapper.py + proton/handlers.py proton/reactor.py proton/utils.py - proton/wrapper.py - proton/_compat.py + + proton/_reactor_impl.py ) # extra files included in the source distribution set(py_dist_files diff --git a/python/proton/__init__.py b/python/proton/__init__.py index 6ee0d68437..be8e247f72 100644 --- a/python/proton/__init__.py +++ b/python/proton/__init__.py @@ -30,54 +30,84 @@ """ from __future__ import absolute_import -from cproton import * -from .wrapper import Wrapper -from . import _compat - import logging -import socket -import sys -import threading -import uuid -import weakref - -# This private NullHandler is required for Python 2.6, -# when we no longer support 2.6 this replace NullHandler class definition and assignment with: -# handler = logging.NullHandler() -class NullHandler(logging.Handler): - def handle(self, record): - pass - - def emit(self, record): - pass - - def createLock(self): - self.lock = None - -handler = NullHandler() - -log = logging.getLogger("proton") -log.addHandler(handler) -def generate_uuid(): - return uuid.uuid4() +from cproton import PN_VERSION_MAJOR, PN_VERSION_MINOR, PN_VERSION_POINT -# -# 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 +from ._condition import Condition +from ._data import UNDESCRIBED, Array, Data, Described, char, symbol, timestamp, ubyte, ushort, uint, ulong, \ + byte, short, int32, float32, decimal32, decimal64, decimal128 +from ._delivery import Delivery, Disposition +from ._endpoints import Endpoint, Connection, Session, Link, Receiver, Sender, Terminus +from ._events import Collector, Event, EventType, Handler +from ._exceptions import ProtonException, MessageException, DataException, TransportException, \ + SSLException, SSLUnavailable, ConnectionException, SessionException, LinkException, Timeout, Interrupt +from ._message import Message, ABORTED, ACCEPTED, PENDING, REJECTED, RELEASED, MODIFIED, SETTLED +from ._transport import Transport, SASL, SSL, SSLDomain, SSLSessionDetails +from ._url import Url +__all__ = [ + "API_LANGUAGE", + "IMPLEMENTATION_LANGUAGE", + "ABORTED", + "ACCEPTED", + "PENDING", + "REJECTED", + "RELEASED", + "MODIFIED", + "SETTLED", + "UNDESCRIBED", + "Array", + "Collector", + "Condition", + "Connection", + "Data", + "DataException", + "Delivery", + "Disposition", + "Described", + "Endpoint", + "Event", + "EventType", + "Handler", + "Link", + "LinkException", + "Message", + "MessageException", + "ProtonException", + "VERSION_MAJOR", + "VERSION_MINOR", + "Receiver", + "SASL", + "Sender", + "Session", + "SessionException", + "SSL", + "SSLDomain", + "SSLSessionDetails", + "SSLUnavailable", + "SSLException", + "Terminus", + "Timeout", + "Interrupt", + "Transport", + "TransportException", + "Url", + "char", + "symbol", + "timestamp", + "ulong", + "byte", + "short", + "int32", + "ubyte", + "ushort", + "uint", + "float32", + "decimal32", + "decimal64", + "decimal128" +] VERSION_MAJOR = PN_VERSION_MAJOR VERSION_MINOR = PN_VERSION_MINOR @@ -86,3603 +116,27 @@ def generate_uuid(): API_LANGUAGE = "C" IMPLEMENTATION_LANGUAGE = "C" -class Constant(object): - - def __init__(self, name): - self.name = name - - def __repr__(self): - return self.name - -class ProtonException(Exception): - """ - The root of the proton exception hierarchy. All proton exception - classes derive from this exception. - """ - pass - -class Timeout(ProtonException): - """ - A timeout exception indicates that a blocking operation has timed - out. - """ - pass - -class Interrupt(ProtonException): - """ - An interrupt exception indicates that a blocking operation was interrupted. - """ - pass - -class MessageException(ProtonException): - """ - The MessageException class is the root of the message exception - hierarchy. All exceptions generated by the Message class derive from - this exception. - """ - pass - -EXCEPTIONS = { - PN_TIMEOUT: Timeout, - PN_INTR: Interrupt - } - -PENDING = Constant("PENDING") -ACCEPTED = Constant("ACCEPTED") -REJECTED = Constant("REJECTED") -RELEASED = Constant("RELEASED") -MODIFIED = Constant("MODIFIED") -ABORTED = Constant("ABORTED") -SETTLED = Constant("SETTLED") - -STATUSES = { - PN_STATUS_ABORTED: ABORTED, - PN_STATUS_ACCEPTED: ACCEPTED, - PN_STATUS_REJECTED: REJECTED, - PN_STATUS_RELEASED: RELEASED, - PN_STATUS_MODIFIED: MODIFIED, - PN_STATUS_PENDING: PENDING, - PN_STATUS_SETTLED: SETTLED, - PN_STATUS_UNKNOWN: None - } - -class Message(object): - """The L{Message} class is a mutable holder of message content. - - @ivar instructions: delivery instructions for the message - @type instructions: dict - @ivar annotations: infrastructure defined message annotations - @type annotations: dict - @ivar properties: application defined message properties - @type properties: dict - @ivar body: message body - @type body: bytes | unicode | dict | list | int | long | float | UUID - """ - - DEFAULT_PRIORITY = PN_DEFAULT_PRIORITY - - def __init__(self, body=None, **kwargs): - """ - @param kwargs: Message property name/value pairs to initialise the Message - """ - self._msg = pn_message() - self._id = Data(pn_message_id(self._msg)) - self._correlation_id = Data(pn_message_correlation_id(self._msg)) - self.instructions = None - self.annotations = None - self.properties = None - self.body = body - for k,v in _compat.iteritems(kwargs): - getattr(self, k) # Raise exception if it's not a valid attribute. - setattr(self, k, v) - - def __del__(self): - if hasattr(self, "_msg"): - pn_message_free(self._msg) - del self._msg - - def _check(self, err): - if err < 0: - exc = EXCEPTIONS.get(err, MessageException) - raise exc("[%s]: %s" % (err, pn_error_text(pn_message_error(self._msg)))) - else: - return err - - def _check_property_keys(self): - for k in self.properties.keys(): - if isinstance(k, unicode): - # py2 unicode, py3 str (via hack definition) - continue - # If key is binary then change to string - elif isinstance(k, str): - # py2 str - self.properties[k.encode('utf-8')] = self.properties.pop(k) - else: - raise MessageException('Application property key is not string type: key=%s %s' % (str(k), type(k))) - - def _pre_encode(self): - inst = Data(pn_message_instructions(self._msg)) - ann = Data(pn_message_annotations(self._msg)) - props = Data(pn_message_properties(self._msg)) - body = Data(pn_message_body(self._msg)) - - inst.clear() - if self.instructions is not None: - inst.put_object(self.instructions) - ann.clear() - if self.annotations is not None: - ann.put_object(self.annotations) - props.clear() - if self.properties is not None: - self._check_property_keys() - props.put_object(self.properties) - body.clear() - if self.body is not None: - body.put_object(self.body) - - def _post_decode(self): - inst = Data(pn_message_instructions(self._msg)) - ann = Data(pn_message_annotations(self._msg)) - props = Data(pn_message_properties(self._msg)) - body = Data(pn_message_body(self._msg)) - - if inst.next(): - self.instructions = inst.get_object() - else: - self.instructions = None - if ann.next(): - self.annotations = ann.get_object() - else: - self.annotations = None - if props.next(): - self.properties = props.get_object() - else: - self.properties = None - if body.next(): - self.body = body.get_object() - else: - self.body = None - - def clear(self): - """ - Clears the contents of the L{Message}. All fields will be reset to - their default values. - """ - pn_message_clear(self._msg) - self.instructions = None - self.annotations = None - self.properties = None - self.body = None - - def _is_inferred(self): - return pn_message_is_inferred(self._msg) - - def _set_inferred(self, value): - self._check(pn_message_set_inferred(self._msg, bool(value))) - - inferred = property(_is_inferred, _set_inferred, doc=""" -The inferred flag for a message indicates how the message content -is encoded into AMQP sections. If inferred is true then binary and -list values in the body of the message will be encoded as AMQP DATA -and AMQP SEQUENCE sections, respectively. If inferred is false, -then all values in the body of the message will be encoded as AMQP -VALUE sections regardless of their type. -""") - - def _is_durable(self): - return pn_message_is_durable(self._msg) - - def _set_durable(self, value): - self._check(pn_message_set_durable(self._msg, bool(value))) - - durable = property(_is_durable, _set_durable, - doc=""" -The durable property indicates that the message should be held durably -by any intermediaries taking responsibility for the message. -""") - - def _get_priority(self): - return pn_message_get_priority(self._msg) - - def _set_priority(self, value): - self._check(pn_message_set_priority(self._msg, value)) - - priority = property(_get_priority, _set_priority, - doc=""" -The priority of the message. -""") - - def _get_ttl(self): - return millis2secs(pn_message_get_ttl(self._msg)) - - def _set_ttl(self, value): - self._check(pn_message_set_ttl(self._msg, secs2millis(value))) - - ttl = property(_get_ttl, _set_ttl, - doc=""" -The time to live of the message measured in seconds. Expired messages -may be dropped. -""") - - def _is_first_acquirer(self): - return pn_message_is_first_acquirer(self._msg) - - def _set_first_acquirer(self, value): - self._check(pn_message_set_first_acquirer(self._msg, bool(value))) - - first_acquirer = property(_is_first_acquirer, _set_first_acquirer, - doc=""" -True iff the recipient is the first to acquire the message. -""") - - def _get_delivery_count(self): - return pn_message_get_delivery_count(self._msg) - - def _set_delivery_count(self, value): - self._check(pn_message_set_delivery_count(self._msg, value)) - - delivery_count = property(_get_delivery_count, _set_delivery_count, - doc=""" -The number of delivery attempts made for this message. -""") - - - def _get_id(self): - return self._id.get_object() - def _set_id(self, value): - if type(value) in (int, long): - value = ulong(value) - self._id.rewind() - self._id.put_object(value) - id = property(_get_id, _set_id, - doc=""" -The id of the message. -""") - - def _get_user_id(self): - return pn_message_get_user_id(self._msg) - - def _set_user_id(self, value): - self._check(pn_message_set_user_id(self._msg, value)) - - user_id = property(_get_user_id, _set_user_id, - doc=""" -The user id of the message creator. -""") - - def _get_address(self): - return utf82unicode(pn_message_get_address(self._msg)) - - def _set_address(self, value): - self._check(pn_message_set_address(self._msg, unicode2utf8(value))) - - address = property(_get_address, _set_address, - doc=""" -The address of the message. -""") - - def _get_subject(self): - return utf82unicode(pn_message_get_subject(self._msg)) - - def _set_subject(self, value): - self._check(pn_message_set_subject(self._msg, unicode2utf8(value))) - - subject = property(_get_subject, _set_subject, - doc=""" -The subject of the message. -""") - - def _get_reply_to(self): - return utf82unicode(pn_message_get_reply_to(self._msg)) - - def _set_reply_to(self, value): - self._check(pn_message_set_reply_to(self._msg, unicode2utf8(value))) - - reply_to = property(_get_reply_to, _set_reply_to, - doc=""" -The reply-to address for the message. -""") - - def _get_correlation_id(self): - return self._correlation_id.get_object() - def _set_correlation_id(self, value): - if type(value) in (int, long): - value = ulong(value) - self._correlation_id.rewind() - self._correlation_id.put_object(value) - - correlation_id = property(_get_correlation_id, _set_correlation_id, - doc=""" -The correlation-id for the message. -""") - - def _get_content_type(self): - return symbol(utf82unicode(pn_message_get_content_type(self._msg))) - - def _set_content_type(self, value): - self._check(pn_message_set_content_type(self._msg, unicode2utf8(value))) - - content_type = property(_get_content_type, _set_content_type, - doc=""" -The content-type of the message. -""") - - def _get_content_encoding(self): - return symbol(utf82unicode(pn_message_get_content_encoding(self._msg))) - - def _set_content_encoding(self, value): - self._check(pn_message_set_content_encoding(self._msg, unicode2utf8(value))) - - content_encoding = property(_get_content_encoding, _set_content_encoding, - doc=""" -The content-encoding of the message. -""") - - def _get_expiry_time(self): - return millis2secs(pn_message_get_expiry_time(self._msg)) - - def _set_expiry_time(self, value): - self._check(pn_message_set_expiry_time(self._msg, secs2millis(value))) - - expiry_time = property(_get_expiry_time, _set_expiry_time, - doc=""" -The expiry time of the message. -""") - - def _get_creation_time(self): - return millis2secs(pn_message_get_creation_time(self._msg)) - - def _set_creation_time(self, value): - self._check(pn_message_set_creation_time(self._msg, secs2millis(value))) - - creation_time = property(_get_creation_time, _set_creation_time, - doc=""" -The creation time of the message. -""") - - def _get_group_id(self): - return utf82unicode(pn_message_get_group_id(self._msg)) - - def _set_group_id(self, value): - self._check(pn_message_set_group_id(self._msg, unicode2utf8(value))) - - group_id = property(_get_group_id, _set_group_id, - doc=""" -The group id of the message. -""") - - def _get_group_sequence(self): - return pn_message_get_group_sequence(self._msg) - - def _set_group_sequence(self, value): - self._check(pn_message_set_group_sequence(self._msg, value)) - - group_sequence = property(_get_group_sequence, _set_group_sequence, - doc=""" -The sequence of the message within its group. -""") - - def _get_reply_to_group_id(self): - return utf82unicode(pn_message_get_reply_to_group_id(self._msg)) - - def _set_reply_to_group_id(self, value): - self._check(pn_message_set_reply_to_group_id(self._msg, unicode2utf8(value))) - - reply_to_group_id = property(_get_reply_to_group_id, _set_reply_to_group_id, - doc=""" -The group-id for any replies. -""") - - def encode(self): - self._pre_encode() - sz = 16 - while True: - err, data = pn_message_encode(self._msg, sz) - if err == PN_OVERFLOW: - sz *= 2 - continue - else: - self._check(err) - return data - - def decode(self, data): - self._check(pn_message_decode(self._msg, data)) - self._post_decode() - - def send(self, sender, tag=None): - dlv = sender.delivery(tag or sender.delivery_tag()) - encoded = self.encode() - sender.stream(encoded) - sender.advance() - if sender.snd_settle_mode == Link.SND_SETTLED: - dlv.settle() - return dlv - - def recv(self, link): - """ - Receives and decodes the message content for the current delivery - from the link. Upon success it will return the current delivery - for the link. If there is no current delivery, or if the current - delivery is incomplete, or if the link is not a receiver, it will - return None. - - @type link: Link - @param link: the link to receive a message from - @return the delivery associated with the decoded message (or None) - - """ - if link.is_sender: return None - dlv = link.current - if not dlv or dlv.partial: return None - dlv.encoded = link.recv(dlv.pending) - link.advance() - # the sender has already forgotten about the delivery, so we might - # as well too - if link.remote_snd_settle_mode == Link.SND_SETTLED: - dlv.settle() - self.decode(dlv.encoded) - return dlv - - def __repr2__(self): - props = [] - for attr in ("inferred", "address", "reply_to", "durable", "ttl", - "priority", "first_acquirer", "delivery_count", "id", - "correlation_id", "user_id", "group_id", "group_sequence", - "reply_to_group_id", "instructions", "annotations", - "properties", "body"): - value = getattr(self, attr) - if value: props.append("%s=%r" % (attr, value)) - return "Message(%s)" % ", ".join(props) - - def __repr__(self): - tmp = pn_string(None) - err = pn_inspect(self._msg, tmp) - result = pn_string_get(tmp) - pn_free(tmp) - self._check(err) - return result - -_DEFAULT = object() - -class Selectable(Wrapper): - - @staticmethod - def wrap(impl): - if impl is None: - return None - else: - return Selectable(impl) - - def __init__(self, impl): - Wrapper.__init__(self, impl, pn_selectable_attachments) - - def _init(self): - pass - - def fileno(self, fd = _DEFAULT): - if fd is _DEFAULT: - return pn_selectable_get_fd(self._impl) - elif fd is None: - pn_selectable_set_fd(self._impl, PN_INVALID_SOCKET) - else: - pn_selectable_set_fd(self._impl, fd) - - def _is_reading(self): - return pn_selectable_is_reading(self._impl) - - def _set_reading(self, val): - pn_selectable_set_reading(self._impl, bool(val)) - - reading = property(_is_reading, _set_reading) - - def _is_writing(self): - return pn_selectable_is_writing(self._impl) - - def _set_writing(self, val): - pn_selectable_set_writing(self._impl, bool(val)) - - writing = property(_is_writing, _set_writing) - - def _get_deadline(self): - tstamp = pn_selectable_get_deadline(self._impl) - if tstamp: - return millis2secs(tstamp) - else: - return None - - def _set_deadline(self, deadline): - pn_selectable_set_deadline(self._impl, secs2millis(deadline)) - - deadline = property(_get_deadline, _set_deadline) - - def readable(self): - pn_selectable_readable(self._impl) - - def writable(self): - pn_selectable_writable(self._impl) - - def expired(self): - pn_selectable_expired(self._impl) - - def _is_registered(self): - return pn_selectable_is_registered(self._impl) - - def _set_registered(self, registered): - pn_selectable_set_registered(self._impl, registered) - - registered = property(_is_registered, _set_registered, - doc=""" -The registered property may be get/set by an I/O polling system to -indicate whether the fd has been registered or not. -""") - - @property - def is_terminal(self): - return pn_selectable_is_terminal(self._impl) - - def terminate(self): - pn_selectable_terminate(self._impl) - - def release(self): - pn_selectable_release(self._impl) - -class DataException(ProtonException): - """ - The DataException class is the root of the Data exception hierarchy. - All exceptions raised by the Data class extend this exception. - """ - pass - -class UnmappedType: - - def __init__(self, msg): - self.msg = msg - - def __repr__(self): - return "UnmappedType(%s)" % self.msg -class ulong(long): - - def __repr__(self): - return "ulong(%s)" % long.__repr__(self) - -class timestamp(long): - - def __repr__(self): - return "timestamp(%s)" % long.__repr__(self) - -class symbol(unicode): - - def __repr__(self): - return "symbol(%s)" % unicode.__repr__(self) - -class char(unicode): - - def __repr__(self): - return "char(%s)" % unicode.__repr__(self) - -class byte(int): - - def __repr__(self): - return "byte(%s)" % int.__repr__(self) - -class short(int): - - def __repr__(self): - return "short(%s)" % int.__repr__(self) - -class int32(int): - - def __repr__(self): - return "int32(%s)" % int.__repr__(self) - -class ubyte(int): - - def __repr__(self): - return "ubyte(%s)" % int.__repr__(self) - -class ushort(int): - - def __repr__(self): - return "ushort(%s)" % int.__repr__(self) - -class uint(long): - - def __repr__(self): - return "uint(%s)" % long.__repr__(self) - -class float32(float): - - def __repr__(self): - return "float32(%s)" % float.__repr__(self) - -class decimal32(int): - - def __repr__(self): - return "decimal32(%s)" % int.__repr__(self) - -class decimal64(long): - - def __repr__(self): - return "decimal64(%s)" % long.__repr__(self) - -class decimal128(bytes): - - def __repr__(self): - return "decimal128(%s)" % bytes.__repr__(self) - -class Described(object): - - 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): - - 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 - -class Data: - """ - The L{Data} class provides an interface for decoding, extracting, - creating, and encoding arbitrary AMQP data. A L{Data} object - contains a tree of AMQP values. Leaf nodes in this tree correspond - to scalars in the AMQP type system such as L{ints} or - L{strings}. Non-leaf nodes in this tree correspond to - compound values in the AMQP type system such as L{lists}, - L{maps}, L{arrays}, or L{described values}. - The root node of the tree is the L{Data} object itself and can have - an arbitrary number of children. - - A L{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 L{next}, L{prev}, - L{enter}, and L{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 I{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: - - - L{NULL} - - L{BOOL} - - L{UBYTE} - - L{USHORT} - - L{SHORT} - - L{UINT} - - L{INT} - - L{ULONG} - - L{LONG} - - L{FLOAT} - - L{DOUBLE} - - L{BINARY} - - L{STRING} - - L{SYMBOL} - - The following types of compound values are supported: - - - L{DESCRIBED} - - L{ARRAY} - - L{LIST} - - L{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" - } - - @classmethod - def type_name(type): return Data.type_names[type] - - def __init__(self, capacity=16): - if type(capacity) in (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. - """ - 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. - """ - 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 next() advances to the first child. - """ - 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 pn_data_exit(self._data) - - def lookup(self, name): - return pn_data_lookup(self._data, name) - - def narrow(self): - pn_data_narrow(self._data) - - def widen(self): - pn_data_widen(self._data) - - def type(self): - """ - Returns the type of the current node. - """ - 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 pn_data_encoded_size(self._data) - - def encode(self): - """ - Returns a representation of the data encoded in AMQP format. - """ - 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 - """ - 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() - """ - 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() - """ - 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 - """ - 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() - """ - self._check(pn_data_put_described(self._data)) - - def put_null(self): - """ - Puts a null value. - """ - self._check(pn_data_put_null(self._data)) - - def put_bool(self, b): - """ - Puts a boolean value. - - @param b: a boolean value - """ - self._check(pn_data_put_bool(self._data, b)) - - def put_ubyte(self, ub): - """ - Puts an unsigned byte value. - - @param ub: an integral value - """ - self._check(pn_data_put_ubyte(self._data, ub)) - - def put_byte(self, b): - """ - Puts a signed byte value. - - @param b: an integral value - """ - self._check(pn_data_put_byte(self._data, b)) - - def put_ushort(self, us): - """ - Puts an unsigned short value. - - @param us: an integral value. - """ - self._check(pn_data_put_ushort(self._data, us)) - - def put_short(self, s): - """ - Puts a signed short value. - - @param s: an integral value - """ - self._check(pn_data_put_short(self._data, s)) - - def put_uint(self, ui): - """ - Puts an unsigned int value. - - @param ui: an integral value - """ - self._check(pn_data_put_uint(self._data, ui)) - - def put_int(self, i): - """ - Puts a signed int value. - - @param i: an integral value - """ - self._check(pn_data_put_int(self._data, i)) - - def put_char(self, c): - """ - Puts a char value. - - @param c: a single character - """ - 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 - """ - self._check(pn_data_put_ulong(self._data, ul)) - - def put_long(self, l): - """ - Puts a signed long value. - - @param l: an integral value - """ - self._check(pn_data_put_long(self._data, l)) - - def put_timestamp(self, t): - """ - Puts a timestamp value. - - @param t: an integral value - """ - self._check(pn_data_put_timestamp(self._data, t)) - - def put_float(self, f): - """ - Puts a float value. - - @param f: a floating point value - """ - self._check(pn_data_put_float(self._data, f)) - - def put_double(self, d): - """ - Puts a double value. - - @param d: a floating point value. - """ - self._check(pn_data_put_double(self._data, d)) - - def put_decimal32(self, d): - """ - Puts a decimal32 value. - - @param d: a decimal32 value - """ - self._check(pn_data_put_decimal32(self._data, d)) - - def put_decimal64(self, d): - """ - Puts a decimal64 value. - - @param d: a decimal64 value - """ - self._check(pn_data_put_decimal64(self._data, d)) - - def put_decimal128(self, d): - """ - Puts a decimal128 value. - - @param d: a decimal128 value - """ - self._check(pn_data_put_decimal128(self._data, d)) - - def put_uuid(self, u): - """ - Puts a UUID value. - - @param u: a uuid value - """ - self._check(pn_data_put_uuid(self._data, u.bytes)) - - def put_binary(self, b): - """ - Puts a binary value. - - @type b: binary - @param b: a binary value - """ - self._check(pn_data_put_binary(self._data, b)) - - def put_memoryview(self, mv): - """Put a python memoryview object as an AMQP binary value""" - self.put_binary(mv.tobytes()) - - def put_buffer(self, buff): - """Put a python buffer object as an AMQP binary value""" - self.put_binary(bytes(buff)) - - def put_string(self, s): - """ - Puts a unicode value. - - @type s: unicode - @param s: a unicode value - """ - self._check(pn_data_put_string(self._data, s.encode("utf8"))) - - def put_symbol(self, s): - """ - Puts a symbolic value. - - @type s: string - @param s: the symbol name - """ - 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 zero. 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 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 zero. 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 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 (0, False, 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() - """ - 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 +# This private NullHandler is required for Python 2.6, +# when we no longer support 2.6 replace this NullHandler class definition and assignment with: +# handler = logging.NullHandler() +class NullHandler(logging.Handler): + def handle(self, record): + pass - 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. + def emit(self, record): + pass - >>> # 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 pn_data_is_described(self._data) + def createLock(self): + self.lock = None - def is_null(self): - """ - Checks if the current node is a null. - """ - return pn_data_is_null(self._data) - def get_bool(self): - """ - If the current node is a boolean, returns its value, returns False - otherwise. - """ - return pn_data_get_bool(self._data) +handler = NullHandler() - def get_ubyte(self): - """ - If the current node is an unsigned byte, returns its value, - returns 0 otherwise. - """ - return ubyte(pn_data_get_ubyte(self._data)) +log = logging.getLogger("proton") +log.addHandler(handler) - def get_byte(self): - """ - If the current node is a signed byte, returns its value, returns 0 - otherwise. - """ - return byte(pn_data_get_byte(self._data)) - def get_ushort(self): - """ - If the current node is an unsigned short, returns its value, - returns 0 otherwise. - """ - return ushort(pn_data_get_ushort(self._data)) - - def get_short(self): - """ - If the current node is a signed short, returns its value, returns - 0 otherwise. - """ - return short(pn_data_get_short(self._data)) - - def get_uint(self): - """ - If the current node is an unsigned int, returns its value, returns - 0 otherwise. - """ - return uint(pn_data_get_uint(self._data)) - - def get_int(self): - """ - If the current node is a signed int, returns its value, returns 0 - otherwise. - """ - return int32(pn_data_get_int(self._data)) - - def get_char(self): - """ - If the current node is a char, returns its value, returns 0 - otherwise. - """ - return char(_compat.unichr(pn_data_get_char(self._data))) - - def get_ulong(self): - """ - If the current node is an unsigned long, returns its value, - returns 0 otherwise. - """ - return ulong(pn_data_get_ulong(self._data)) - - def get_long(self): - """ - If the current node is an signed long, returns its value, returns - 0 otherwise. - """ - return long(pn_data_get_long(self._data)) - - def get_timestamp(self): - """ - If the current node is a timestamp, returns its value, returns 0 - otherwise. - """ - return timestamp(pn_data_get_timestamp(self._data)) - - def get_float(self): - """ - If the current node is a float, returns its value, raises 0 - otherwise. - """ - return float32(pn_data_get_float(self._data)) - - def get_double(self): - """ - If the current node is a double, returns its value, returns 0 - otherwise. - """ - return pn_data_get_double(self._data) - - # XXX: need to convert - def get_decimal32(self): - """ - If the current node is a decimal32, returns its value, returns 0 - otherwise. - """ - return decimal32(pn_data_get_decimal32(self._data)) - - # XXX: need to convert - def get_decimal64(self): - """ - If the current node is a decimal64, returns its value, returns 0 - otherwise. - """ - return decimal64(pn_data_get_decimal64(self._data)) - - # XXX: need to convert - def get_decimal128(self): - """ - If the current node is a decimal128, returns its value, returns 0 - otherwise. - """ - return decimal128(pn_data_get_decimal128(self._data)) - - def get_uuid(self): - """ - If the current node is a UUID, returns its value, returns None - otherwise. - """ - 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): - """ - If the current node is binary, returns its value, returns "" - otherwise. - """ - return pn_data_get_binary(self._data) - - def get_string(self): - """ - If the current node is a string, returns its value, returns "" - otherwise. - """ - return pn_data_get_string(self._data).decode("utf8") - - def get_symbol(self): - """ - If the current node is a symbol, returns its value, returns "" - otherwise. - """ - return symbol(pn_data_get_symbol(self._data).decode('ascii')) - - def copy(self, src): - self._check(pn_data_copy(self._data, src._data)) - - def format(self): - 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): - pn_data_dump(self._data) - - def put_dict(self, d): - 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): - 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): - self.put_list() - self.enter() - try: - for o in s: - self.put_object(o) - finally: - self.exit() - - def get_sequence(self): - if self.enter(): - try: - result = [] - while self.next(): - result.append(self.get_object()) - finally: - self.exit() - return result - - def get_py_described(self): - 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): - self.put_described() - self.enter() - try: - self.put_object(d.descriptor) - self.put_object(d.value) - finally: - self.exit() - - def get_py_array(self): - """ - If the current node is an array, return an Array object - representing the array and its contents. Otherwise return None. - This is a convenience wrapper around get_array, enter, etc. - """ - - 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): - 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 - } - # 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)) - -class ConnectionException(ProtonException): - pass - -class Endpoint(object): - - LOCAL_UNINIT = PN_LOCAL_UNINIT - REMOTE_UNINIT = PN_REMOTE_UNINIT - LOCAL_ACTIVE = PN_LOCAL_ACTIVE - REMOTE_ACTIVE = PN_REMOTE_ACTIVE - LOCAL_CLOSED = PN_LOCAL_CLOSED - REMOTE_CLOSED = PN_REMOTE_CLOSED - - def _init(self): - self.condition = None - - def _update_cond(self): - obj2cond(self.condition, self._get_cond_impl()) - - @property - def remote_condition(self): - return cond2obj(self._get_remote_cond_impl()) - - # the following must be provided by subclasses - def _get_cond_impl(self): - assert False, "Subclass must override this!" - - def _get_remote_cond_impl(self): - assert False, "Subclass must override this!" - - def _get_handler(self): - from . import reactor - ractor = reactor.Reactor.wrap(pn_object_reactor(self._impl)) - if ractor: - on_error = ractor.on_error_delegate() - else: - on_error = None - record = self._get_attachments() - return WrappedHandler.wrap(pn_record_get_handler(record), on_error) - - def _set_handler(self, handler): - from . import reactor - ractor = reactor.Reactor.wrap(pn_object_reactor(self._impl)) - if ractor: - on_error = ractor.on_error_delegate() - else: - on_error = None - impl = _chandler(handler, on_error) - record = self._get_attachments() - pn_record_set_handler(record, impl) - pn_decref(impl) - - handler = property(_get_handler, _set_handler) - - @property - def transport(self): - return self.connection.transport - -class Condition: - - def __init__(self, name, description=None, info=None): - self.name = name - self.description = description - self.info = info - - def __repr__(self): - return "Condition(%s)" % ", ".join([repr(x) for x in - (self.name, self.description, self.info) - if x]) - - def __eq__(self, o): - if not isinstance(o, Condition): return False - return self.name == o.name and \ - self.description == o.description and \ - self.info == o.info - -def obj2cond(obj, cond): - pn_condition_clear(cond) - if obj: - pn_condition_set_name(cond, str(obj.name)) - pn_condition_set_description(cond, obj.description) - info = Data(pn_condition_info(cond)) - if obj.info: - info.put_object(obj.info) - -def cond2obj(cond): - if pn_condition_is_set(cond): - return Condition(pn_condition_get_name(cond), - pn_condition_get_description(cond), - dat2obj(pn_condition_info(cond))) - else: - return None - -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) - -def secs2millis(secs): - return long(secs*1000) - -def millis2secs(millis): - return float(millis)/1000.0 - -def timeout2millis(secs): - if secs is None: return PN_MILLIS_MAX - return secs2millis(secs) - -def millis2timeout(millis): - if millis == PN_MILLIS_MAX: return None - return millis2secs(millis) - -def unicode2utf8(string): - """Some Proton APIs expect a null terminated string. Convert python text - types to UTF8 to avoid zero bytes introduced by other multi-byte encodings. - This method will throw if the string cannot be converted. - """ - if string is None: - return None - elif isinstance(string, str): - # Must be py2 or py3 str - # The swig binding converts py3 str -> utf8 char* and back sutomatically - return string - elif isinstance(string, unicode): - # This must be python2 unicode as we already detected py3 str above - return string.encode('utf-8') - # Anything else illegal - specifically python3 bytes - raise TypeError("Unrecognized string type: %r (%s)" % (string, type(string))) - -def utf82unicode(string): - """Convert C strings returned from proton-c into python unicode""" - if string is None: - return None - elif isinstance(string, unicode): - # py2 unicode, py3 str (via hack definition) - return string - elif isinstance(string, bytes): - # py2 str (via hack definition), py3 bytes - return string.decode('utf8') - raise TypeError("Unrecognized string type") - -class Connection(Wrapper, Endpoint): - """ - A representation of an AMQP connection - """ - - @staticmethod - def wrap(impl): - if impl is None: - return None - else: - return Connection(impl) - - def __init__(self, impl = pn_connection): - Wrapper.__init__(self, impl, pn_connection_attachments) - - def _init(self): - Endpoint._init(self) - self.offered_capabilities = None - self.desired_capabilities = None - self.properties = None - - def _get_attachments(self): - return pn_connection_attachments(self._impl) - - @property - def connection(self): - return self - - @property - def transport(self): - return Transport.wrap(pn_connection_transport(self._impl)) - - def _check(self, err): - if err < 0: - exc = EXCEPTIONS.get(err, ConnectionException) - raise exc("[%s]: %s" % (err, pn_connection_error(self._impl))) - else: - return err - - def _get_cond_impl(self): - return pn_connection_condition(self._impl) - - def _get_remote_cond_impl(self): - return pn_connection_remote_condition(self._impl) - - def collect(self, collector): - if collector is None: - pn_connection_collect(self._impl, None) - else: - pn_connection_collect(self._impl, collector._impl) - self._collector = weakref.ref(collector) - - def _get_container(self): - return utf82unicode(pn_connection_get_container(self._impl)) - def _set_container(self, name): - return pn_connection_set_container(self._impl, unicode2utf8(name)) - - container = property(_get_container, _set_container) - - def _get_hostname(self): - return utf82unicode(pn_connection_get_hostname(self._impl)) - def _set_hostname(self, name): - return pn_connection_set_hostname(self._impl, unicode2utf8(name)) - - hostname = property(_get_hostname, _set_hostname, - doc=""" -Set the name of the host (either fully qualified or relative) to which this -connection is connecting to. This information may be used by the remote -peer to determine the correct back-end service to connect the client to. -This value will be sent in the Open performative, and will be used by SSL -and SASL layers to identify the peer. -""") - - def _get_user(self): - return utf82unicode(pn_connection_get_user(self._impl)) - def _set_user(self, name): - return pn_connection_set_user(self._impl, unicode2utf8(name)) - - user = property(_get_user, _set_user) - - def _get_password(self): - return None - def _set_password(self, name): - return pn_connection_set_password(self._impl, unicode2utf8(name)) - - password = property(_get_password, _set_password) - - @property - def remote_container(self): - """The container identifier specified by the remote peer for this connection.""" - return pn_connection_remote_container(self._impl) - - @property - def remote_hostname(self): - """The hostname specified by the remote peer for this connection.""" - return pn_connection_remote_hostname(self._impl) - - @property - def remote_offered_capabilities(self): - """The capabilities offered by the remote peer for this connection.""" - return dat2obj(pn_connection_remote_offered_capabilities(self._impl)) - - @property - def remote_desired_capabilities(self): - """The capabilities desired by the remote peer for this connection.""" - return dat2obj(pn_connection_remote_desired_capabilities(self._impl)) - - @property - def remote_properties(self): - """The properties specified by the remote peer for this connection.""" - return dat2obj(pn_connection_remote_properties(self._impl)) - - def open(self): - """ - Opens the connection. - - In more detail, this moves the local state of the connection to - the ACTIVE state and triggers an open frame to be sent to the - peer. A connection is fully active once both peers have opened it. - """ - obj2dat(self.offered_capabilities, - pn_connection_offered_capabilities(self._impl)) - obj2dat(self.desired_capabilities, - pn_connection_desired_capabilities(self._impl)) - obj2dat(self.properties, pn_connection_properties(self._impl)) - pn_connection_open(self._impl) - - def close(self): - """ - Closes the connection. - - In more detail, this moves the local state of the connection to - the CLOSED state and triggers a close frame to be sent to the - peer. A connection is fully closed once both peers have closed it. - """ - self._update_cond() - pn_connection_close(self._impl) - if hasattr(self, '_session_policy'): - # break circular ref - del self._session_policy - - @property - def state(self): - """ - The state of the connection as a bit field. The state has a local - and a remote component. Each of these can be in one of three - states: UNINIT, ACTIVE or CLOSED. These can be tested by masking - against LOCAL_UNINIT, LOCAL_ACTIVE, LOCAL_CLOSED, REMOTE_UNINIT, - REMOTE_ACTIVE and REMOTE_CLOSED. - """ - return pn_connection_state(self._impl) - - def session(self): - """ - Returns a new session on this connection. - """ - ssn = pn_session(self._impl) - if ssn is None: - raise(SessionException("Session allocation failed.")) - else: - return Session(ssn) - - def session_head(self, mask): - return Session.wrap(pn_session_head(self._impl, mask)) - - def link_head(self, mask): - return Link.wrap(pn_link_head(self._impl, mask)) - - @property - def work_head(self): - return Delivery.wrap(pn_work_head(self._impl)) - - @property - def error(self): - return pn_error_code(pn_connection_error(self._impl)) - - def free(self): - pn_connection_release(self._impl) - -class SessionException(ProtonException): - pass - -class Session(Wrapper, Endpoint): - - @staticmethod - def wrap(impl): - if impl is None: - return None - else: - return Session(impl) - - def __init__(self, impl): - Wrapper.__init__(self, impl, pn_session_attachments) - - def _get_attachments(self): - return pn_session_attachments(self._impl) - - def _get_cond_impl(self): - return pn_session_condition(self._impl) - - def _get_remote_cond_impl(self): - return pn_session_remote_condition(self._impl) - - def _get_incoming_capacity(self): - return pn_session_get_incoming_capacity(self._impl) - - def _set_incoming_capacity(self, capacity): - pn_session_set_incoming_capacity(self._impl, capacity) - - incoming_capacity = property(_get_incoming_capacity, _set_incoming_capacity) - - def _get_outgoing_window(self): - return pn_session_get_outgoing_window(self._impl) - - def _set_outgoing_window(self, window): - pn_session_set_outgoing_window(self._impl, window) - - outgoing_window = property(_get_outgoing_window, _set_outgoing_window) - - @property - def outgoing_bytes(self): - return pn_session_outgoing_bytes(self._impl) - - @property - def incoming_bytes(self): - return pn_session_incoming_bytes(self._impl) - - def open(self): - pn_session_open(self._impl) - - def close(self): - self._update_cond() - pn_session_close(self._impl) - - def next(self, mask): - return Session.wrap(pn_session_next(self._impl, mask)) - - @property - def state(self): - return pn_session_state(self._impl) - - @property - def connection(self): - return Connection.wrap(pn_session_connection(self._impl)) - - def sender(self, name): - return Sender(pn_sender(self._impl, unicode2utf8(name))) - - def receiver(self, name): - return Receiver(pn_receiver(self._impl, unicode2utf8(name))) - - def free(self): - pn_session_free(self._impl) - -class LinkException(ProtonException): - pass - -class Link(Wrapper, Endpoint): - """ - A representation of an AMQP link, of which there are two concrete - implementations, Sender and Receiver. - """ - - SND_UNSETTLED = PN_SND_UNSETTLED - SND_SETTLED = PN_SND_SETTLED - SND_MIXED = PN_SND_MIXED - - RCV_FIRST = PN_RCV_FIRST - RCV_SECOND = PN_RCV_SECOND - - @staticmethod - def wrap(impl): - if impl is None: return None - if pn_link_is_sender(impl): - return Sender(impl) - else: - return Receiver(impl) - - def __init__(self, impl): - Wrapper.__init__(self, impl, pn_link_attachments) - - def _get_attachments(self): - return pn_link_attachments(self._impl) - - def _check(self, err): - if err < 0: - exc = EXCEPTIONS.get(err, LinkException) - raise exc("[%s]: %s" % (err, pn_error_text(pn_link_error(self._impl)))) - else: - return err - - def _get_cond_impl(self): - return pn_link_condition(self._impl) - - def _get_remote_cond_impl(self): - return pn_link_remote_condition(self._impl) - - def open(self): - """ - Opens the link. - - In more detail, this moves the local state of the link to the - ACTIVE state and triggers an attach frame to be sent to the - peer. A link is fully active once both peers have attached it. - """ - pn_link_open(self._impl) - - def close(self): - """ - Closes the link. - - In more detail, this moves the local state of the link to the - CLOSED state and triggers an detach frame (with the closed flag - set) to be sent to the peer. A link is fully closed once both - peers have detached it. - """ - self._update_cond() - pn_link_close(self._impl) - - @property - def state(self): - """ - The state of the link as a bit field. The state has a local - and a remote component. Each of these can be in one of three - states: UNINIT, ACTIVE or CLOSED. These can be tested by masking - against LOCAL_UNINIT, LOCAL_ACTIVE, LOCAL_CLOSED, REMOTE_UNINIT, - REMOTE_ACTIVE and REMOTE_CLOSED. - """ - return pn_link_state(self._impl) - - @property - def source(self): - """The source of the link as described by the local peer.""" - return Terminus(pn_link_source(self._impl)) - - @property - def target(self): - """The target of the link as described by the local peer.""" - return Terminus(pn_link_target(self._impl)) - - @property - def remote_source(self): - """The source of the link as described by the remote peer.""" - return Terminus(pn_link_remote_source(self._impl)) - @property - def remote_target(self): - """The target of the link as described by the remote peer.""" - return Terminus(pn_link_remote_target(self._impl)) - - @property - def session(self): - return Session.wrap(pn_link_session(self._impl)) - - @property - def connection(self): - """The connection on which this link was attached.""" - return self.session.connection - - def delivery(self, tag): - return Delivery(pn_delivery(self._impl, tag)) - - @property - def current(self): - return Delivery.wrap(pn_link_current(self._impl)) - - def advance(self): - return pn_link_advance(self._impl) - - @property - def unsettled(self): - return pn_link_unsettled(self._impl) - - @property - def credit(self): - """The amount of outstanding credit on this link.""" - return pn_link_credit(self._impl) - - @property - def available(self): - return pn_link_available(self._impl) - - @property - def queued(self): - return pn_link_queued(self._impl) - - def next(self, mask): - return Link.wrap(pn_link_next(self._impl, mask)) - - @property - def name(self): - """Returns the name of the link""" - return utf82unicode(pn_link_name(self._impl)) - - @property - def is_sender(self): - """Returns true if this link is a sender.""" - return pn_link_is_sender(self._impl) - - @property - def is_receiver(self): - """Returns true if this link is a receiver.""" - return pn_link_is_receiver(self._impl) - - @property - def remote_snd_settle_mode(self): - return pn_link_remote_snd_settle_mode(self._impl) - - @property - def remote_rcv_settle_mode(self): - return pn_link_remote_rcv_settle_mode(self._impl) - - def _get_snd_settle_mode(self): - return pn_link_snd_settle_mode(self._impl) - def _set_snd_settle_mode(self, mode): - pn_link_set_snd_settle_mode(self._impl, mode) - snd_settle_mode = property(_get_snd_settle_mode, _set_snd_settle_mode) - - def _get_rcv_settle_mode(self): - return pn_link_rcv_settle_mode(self._impl) - def _set_rcv_settle_mode(self, mode): - pn_link_set_rcv_settle_mode(self._impl, mode) - rcv_settle_mode = property(_get_rcv_settle_mode, _set_rcv_settle_mode) - - def _get_drain(self): - return pn_link_get_drain(self._impl) - - def _set_drain(self, b): - pn_link_set_drain(self._impl, bool(b)) - - drain_mode = property(_get_drain, _set_drain) - - def drained(self): - return pn_link_drained(self._impl) - - @property - def remote_max_message_size(self): - return pn_link_remote_max_message_size(self._impl) - - def _get_max_message_size(self): - return pn_link_max_message_size(self._impl) - def _set_max_message_size(self, mode): - pn_link_set_max_message_size(self._impl, mode) - max_message_size = property(_get_max_message_size, _set_max_message_size) - - def detach(self): - return pn_link_detach(self._impl) - - def free(self): - pn_link_free(self._impl) - -class Terminus(object): - - UNSPECIFIED = PN_UNSPECIFIED - SOURCE = PN_SOURCE - TARGET = PN_TARGET - COORDINATOR = PN_COORDINATOR - - NONDURABLE = PN_NONDURABLE - CONFIGURATION = PN_CONFIGURATION - DELIVERIES = PN_DELIVERIES - - DIST_MODE_UNSPECIFIED = PN_DIST_MODE_UNSPECIFIED - DIST_MODE_COPY = PN_DIST_MODE_COPY - DIST_MODE_MOVE = PN_DIST_MODE_MOVE - - EXPIRE_WITH_LINK = PN_EXPIRE_WITH_LINK - EXPIRE_WITH_SESSION = PN_EXPIRE_WITH_SESSION - EXPIRE_WITH_CONNECTION = PN_EXPIRE_WITH_CONNECTION - EXPIRE_NEVER = PN_EXPIRE_NEVER - - def __init__(self, impl): - self._impl = impl - - def _check(self, err): - if err < 0: - exc = EXCEPTIONS.get(err, LinkException) - raise exc("[%s]" % err) - else: - return err - - def _get_type(self): - return pn_terminus_get_type(self._impl) - def _set_type(self, type): - self._check(pn_terminus_set_type(self._impl, type)) - type = property(_get_type, _set_type) - - def _get_address(self): - """The address that identifies the source or target node""" - return utf82unicode(pn_terminus_get_address(self._impl)) - def _set_address(self, address): - self._check(pn_terminus_set_address(self._impl, unicode2utf8(address))) - address = property(_get_address, _set_address) - - def _get_durability(self): - return pn_terminus_get_durability(self._impl) - def _set_durability(self, seconds): - self._check(pn_terminus_set_durability(self._impl, seconds)) - durability = property(_get_durability, _set_durability) - - def _get_expiry_policy(self): - return pn_terminus_get_expiry_policy(self._impl) - def _set_expiry_policy(self, seconds): - self._check(pn_terminus_set_expiry_policy(self._impl, seconds)) - expiry_policy = property(_get_expiry_policy, _set_expiry_policy) - - def _get_timeout(self): - return pn_terminus_get_timeout(self._impl) - def _set_timeout(self, seconds): - self._check(pn_terminus_set_timeout(self._impl, seconds)) - timeout = property(_get_timeout, _set_timeout) - - def _is_dynamic(self): - """Indicates whether the source or target node was dynamically - created""" - return pn_terminus_is_dynamic(self._impl) - def _set_dynamic(self, dynamic): - self._check(pn_terminus_set_dynamic(self._impl, dynamic)) - dynamic = property(_is_dynamic, _set_dynamic) - - def _get_distribution_mode(self): - return pn_terminus_get_distribution_mode(self._impl) - def _set_distribution_mode(self, mode): - self._check(pn_terminus_set_distribution_mode(self._impl, mode)) - distribution_mode = property(_get_distribution_mode, _set_distribution_mode) - - @property - def properties(self): - """Properties of a dynamic source or target.""" - return Data(pn_terminus_properties(self._impl)) - - @property - def capabilities(self): - """Capabilities of the source or target.""" - return Data(pn_terminus_capabilities(self._impl)) - - @property - def outcomes(self): - return Data(pn_terminus_outcomes(self._impl)) - - @property - def filter(self): - """A filter on a source allows the set of messages transfered over - the link to be restricted""" - return Data(pn_terminus_filter(self._impl)) - - def copy(self, src): - self._check(pn_terminus_copy(self._impl, src._impl)) - -class Sender(Link): - """ - A link over which messages are sent. - """ - - def offered(self, n): - pn_link_offered(self._impl, n) - - def stream(self, data): - """ - Send specified data as part of the current delivery - - @type data: binary - @param data: data to send - """ - return self._check(pn_link_send(self._impl, data)) - - def send(self, obj, tag=None): - """ - Send specified object over this sender; the object is expected to - have a send() method on it that takes the sender and an optional - tag as arguments. - - Where the object is a Message, this will send the message over - this link, creating a new delivery for the purpose. - """ - if hasattr(obj, 'send'): - return obj.send(self, tag=tag) - else: - # treat object as bytes - return self.stream(obj) - - def delivery_tag(self): - if not hasattr(self, 'tag_generator'): - def simple_tags(): - count = 1 - while True: - yield str(count) - count += 1 - self.tag_generator = simple_tags() - return next(self.tag_generator) - -class Receiver(Link): - """ - A link over which messages are received. - """ - - def flow(self, n): - """Increases the credit issued to the remote sender by the specified number of messages.""" - pn_link_flow(self._impl, n) - - def recv(self, limit): - n, binary = pn_link_recv(self._impl, limit) - if n == PN_EOS: - return None - else: - self._check(n) - return binary - - def drain(self, n): - pn_link_drain(self._impl, n) - - def draining(self): - return pn_link_draining(self._impl) - -class NamedInt(int): - - values = {} - - def __new__(cls, i, name): - ni = super(NamedInt, cls).__new__(cls, i) - cls.values[i] = ni - return ni - - def __init__(self, i, name): - self.name = name - - def __repr__(self): - return self.name - - def __str__(self): - return self.name - - @classmethod - def get(cls, i): - return cls.values.get(i, i) - -class DispositionType(NamedInt): - values = {} - -class Disposition(object): - - RECEIVED = DispositionType(PN_RECEIVED, "RECEIVED") - ACCEPTED = DispositionType(PN_ACCEPTED, "ACCEPTED") - REJECTED = DispositionType(PN_REJECTED, "REJECTED") - RELEASED = DispositionType(PN_RELEASED, "RELEASED") - MODIFIED = DispositionType(PN_MODIFIED, "MODIFIED") - - def __init__(self, impl, local): - self._impl = impl - self.local = local - self._data = None - self._condition = None - self._annotations = None - - @property - def type(self): - return DispositionType.get(pn_disposition_type(self._impl)) - - def _get_section_number(self): - return pn_disposition_get_section_number(self._impl) - def _set_section_number(self, n): - pn_disposition_set_section_number(self._impl, n) - section_number = property(_get_section_number, _set_section_number) - - def _get_section_offset(self): - return pn_disposition_get_section_offset(self._impl) - def _set_section_offset(self, n): - pn_disposition_set_section_offset(self._impl, n) - section_offset = property(_get_section_offset, _set_section_offset) - - def _get_failed(self): - return pn_disposition_is_failed(self._impl) - def _set_failed(self, b): - pn_disposition_set_failed(self._impl, b) - failed = property(_get_failed, _set_failed) - - def _get_undeliverable(self): - return pn_disposition_is_undeliverable(self._impl) - def _set_undeliverable(self, b): - pn_disposition_set_undeliverable(self._impl, b) - undeliverable = property(_get_undeliverable, _set_undeliverable) - - def _get_data(self): - if self.local: - return self._data - else: - return dat2obj(pn_disposition_data(self._impl)) - def _set_data(self, obj): - if self.local: - self._data = obj - else: - raise AttributeError("data attribute is read-only") - data = property(_get_data, _set_data) - - def _get_annotations(self): - if self.local: - return self._annotations - else: - return dat2obj(pn_disposition_annotations(self._impl)) - def _set_annotations(self, obj): - if self.local: - self._annotations = obj - else: - raise AttributeError("annotations attribute is read-only") - annotations = property(_get_annotations, _set_annotations) - - def _get_condition(self): - if self.local: - return self._condition - else: - return cond2obj(pn_disposition_condition(self._impl)) - def _set_condition(self, obj): - if self.local: - self._condition = obj - else: - raise AttributeError("condition attribute is read-only") - condition = property(_get_condition, _set_condition) - -class Delivery(Wrapper): - """ - Tracks and/or records the delivery of a message over a link. - """ - - RECEIVED = Disposition.RECEIVED - ACCEPTED = Disposition.ACCEPTED - REJECTED = Disposition.REJECTED - RELEASED = Disposition.RELEASED - MODIFIED = Disposition.MODIFIED - - @staticmethod - def wrap(impl): - if impl is None: - return None - else: - return Delivery(impl) - - def __init__(self, impl): - Wrapper.__init__(self, impl, pn_delivery_attachments) - - def _init(self): - self.local = Disposition(pn_delivery_local(self._impl), True) - self.remote = Disposition(pn_delivery_remote(self._impl), False) - - @property - def tag(self): - """The identifier for the delivery.""" - return pn_delivery_tag(self._impl) - - @property - def writable(self): - """Returns true for an outgoing delivery to which data can now be written.""" - return pn_delivery_writable(self._impl) - - @property - def readable(self): - """Returns true for an incoming delivery that has data to read.""" - return pn_delivery_readable(self._impl) - - @property - def updated(self): - """Returns true if the state of the delivery has been updated - (e.g. it has been settled and/or accepted, rejected etc).""" - return pn_delivery_updated(self._impl) - - def update(self, state): - """ - Set the local state of the delivery e.g. ACCEPTED, REJECTED, RELEASED. - """ - obj2dat(self.local._data, pn_disposition_data(self.local._impl)) - obj2dat(self.local._annotations, pn_disposition_annotations(self.local._impl)) - obj2cond(self.local._condition, pn_disposition_condition(self.local._impl)) - pn_delivery_update(self._impl, state) - - @property - def pending(self): - return pn_delivery_pending(self._impl) - - @property - def partial(self): - """ - Returns true for an incoming delivery if not all the data is - yet available. - """ - return pn_delivery_partial(self._impl) - - @property - def local_state(self): - """Returns the local state of the delivery.""" - return DispositionType.get(pn_delivery_local_state(self._impl)) - - @property - def remote_state(self): - """ - Returns the state of the delivery as indicated by the remote - peer. - """ - return DispositionType.get(pn_delivery_remote_state(self._impl)) - - @property - def settled(self): - """ - Returns true if the delivery has been settled by the remote peer. - """ - return pn_delivery_settled(self._impl) - - def settle(self): - """ - Settles the delivery locally. This indicates the application - considers the delivery complete and does not wish to receive any - further events about it. Every delivery should be settled locally. - """ - pn_delivery_settle(self._impl) - - @property - def aborted(self): - """Returns true if the delivery has been aborted.""" - return pn_delivery_aborted(self._impl) - - def abort(self): - """ - Aborts the delivery. This indicates the application wishes to - invalidate any data that may have already been sent on this delivery. - The delivery cannot be aborted after it has been completely delivered. - """ - pn_delivery_abort(self._impl) - - @property - def work_next(self): - return Delivery.wrap(pn_work_next(self._impl)) - - @property - def link(self): - """ - Returns the link on which the delivery was sent or received. - """ - return Link.wrap(pn_delivery_link(self._impl)) - - @property - def session(self): - """ - Returns the session over which the delivery was sent or received. - """ - return self.link.session - - @property - def connection(self): - """ - Returns the connection over which the delivery was sent or received. - """ - return self.session.connection - - @property - def transport(self): - return self.connection.transport - -class TransportException(ProtonException): - pass - -class TraceAdapter: - - def __init__(self, tracer): - self.tracer = tracer - - def __call__(self, trans_impl, message): - self.tracer(Transport.wrap(trans_impl), message) - -class Transport(Wrapper): - - TRACE_OFF = PN_TRACE_OFF - TRACE_DRV = PN_TRACE_DRV - TRACE_FRM = PN_TRACE_FRM - TRACE_RAW = PN_TRACE_RAW - - CLIENT = 1 - SERVER = 2 - - @staticmethod - def wrap(impl): - if impl is None: - return None - else: - return Transport(_impl=impl) - - def __init__(self, mode=None, _impl = pn_transport): - Wrapper.__init__(self, _impl, pn_transport_attachments) - if mode == Transport.SERVER: - pn_transport_set_server(self._impl) - elif mode is None or mode==Transport.CLIENT: - pass - else: - raise TransportException("Cannot initialise Transport from mode: %s" % str(mode)) - - def _init(self): - self._sasl = None - self._ssl = None - - def _check(self, err): - if err < 0: - exc = EXCEPTIONS.get(err, TransportException) - raise exc("[%s]: %s" % (err, pn_error_text(pn_transport_error(self._impl)))) - else: - return err - - def _set_tracer(self, tracer): - pn_transport_set_pytracer(self._impl, TraceAdapter(tracer)); - - def _get_tracer(self): - adapter = pn_transport_get_pytracer(self._impl) - if adapter: - return adapter.tracer - else: - return None - - tracer = property(_get_tracer, _set_tracer, - doc=""" -A callback for trace logging. The callback is passed the transport and log message. -""") - - def log(self, message): - pn_transport_log(self._impl, message) - - def require_auth(self, bool): - pn_transport_require_auth(self._impl, bool) - - @property - def authenticated(self): - return pn_transport_is_authenticated(self._impl) - - def require_encryption(self, bool): - pn_transport_require_encryption(self._impl, bool) - - @property - def encrypted(self): - return pn_transport_is_encrypted(self._impl) - - @property - def user(self): - return pn_transport_get_user(self._impl) - - def bind(self, connection): - """Assign a connection to the transport""" - self._check(pn_transport_bind(self._impl, connection._impl)) - - def unbind(self): - """Release the connection""" - self._check(pn_transport_unbind(self._impl)) - - def trace(self, n): - pn_transport_trace(self._impl, n) - - def tick(self, now): - """Process any timed events (like heartbeat generation). - now = seconds since epoch (float). - """ - return millis2secs(pn_transport_tick(self._impl, secs2millis(now))) - - def capacity(self): - c = pn_transport_capacity(self._impl) - if c >= PN_EOS: - return c - else: - return self._check(c) - - def push(self, binary): - n = self._check(pn_transport_push(self._impl, binary)) - if n != len(binary): - raise OverflowError("unable to process all bytes: %s, %s" % (n, len(binary))) - - def close_tail(self): - self._check(pn_transport_close_tail(self._impl)) - - def pending(self): - p = pn_transport_pending(self._impl) - if p >= PN_EOS: - return p - else: - return self._check(p) - - def peek(self, size): - cd, out = pn_transport_peek(self._impl, size) - if cd == PN_EOS: - return None - else: - self._check(cd) - return out - - def pop(self, size): - pn_transport_pop(self._impl, size) - - def close_head(self): - self._check(pn_transport_close_head(self._impl)) - - @property - def closed(self): - return pn_transport_closed(self._impl) - - # AMQP 1.0 max-frame-size - def _get_max_frame_size(self): - return pn_transport_get_max_frame(self._impl) - - def _set_max_frame_size(self, value): - pn_transport_set_max_frame(self._impl, value) - - max_frame_size = property(_get_max_frame_size, _set_max_frame_size, - doc=""" -Sets the maximum size for received frames (in bytes). -""") - - @property - def remote_max_frame_size(self): - return pn_transport_get_remote_max_frame(self._impl) - - def _get_channel_max(self): - return pn_transport_get_channel_max(self._impl) - - def _set_channel_max(self, value): - if pn_transport_set_channel_max(self._impl, value): - raise SessionException("Too late to change channel max.") - - channel_max = property(_get_channel_max, _set_channel_max, - doc=""" -Sets the maximum channel that may be used on the transport. -""") - - @property - def remote_channel_max(self): - return pn_transport_remote_channel_max(self._impl) - - # AMQP 1.0 idle-time-out - def _get_idle_timeout(self): - return millis2secs(pn_transport_get_idle_timeout(self._impl)) - - def _set_idle_timeout(self, sec): - pn_transport_set_idle_timeout(self._impl, secs2millis(sec)) - - idle_timeout = property(_get_idle_timeout, _set_idle_timeout, - doc=""" -The idle timeout of the connection (float, in seconds). -""") - - @property - def remote_idle_timeout(self): - return millis2secs(pn_transport_get_remote_idle_timeout(self._impl)) - - @property - def frames_output(self): - return pn_transport_get_frames_output(self._impl) - - @property - def frames_input(self): - return pn_transport_get_frames_input(self._impl) - - def sasl(self): - return SASL(self) - - def ssl(self, domain=None, session_details=None): - # SSL factory (singleton for this transport) - if not self._ssl: - self._ssl = SSL(self, domain, session_details) - return self._ssl - - @property - def condition(self): - return cond2obj(pn_transport_condition(self._impl)) - - @property - def connection(self): - return Connection.wrap(pn_transport_connection(self._impl)) - -class SASLException(TransportException): - pass - -class SASL(Wrapper): - - OK = PN_SASL_OK - AUTH = PN_SASL_AUTH - SYS = PN_SASL_SYS - PERM = PN_SASL_PERM - TEMP = PN_SASL_TEMP - - @staticmethod - def extended(): - return pn_sasl_extended() - - def __init__(self, transport): - Wrapper.__init__(self, transport._impl, pn_transport_attachments) - self._sasl = pn_sasl(transport._impl) - - def _check(self, err): - if err < 0: - exc = EXCEPTIONS.get(err, SASLException) - raise exc("[%s]" % (err)) - else: - return err - - @property - def user(self): - return pn_sasl_get_user(self._sasl) - - @property - def mech(self): - return pn_sasl_get_mech(self._sasl) - - @property - def outcome(self): - outcome = pn_sasl_outcome(self._sasl) - if outcome == PN_SASL_NONE: - return None - else: - return outcome - - def allowed_mechs(self, mechs): - pn_sasl_allowed_mechs(self._sasl, unicode2utf8(mechs)) - - def _get_allow_insecure_mechs(self): - return pn_sasl_get_allow_insecure_mechs(self._sasl) - - def _set_allow_insecure_mechs(self, insecure): - pn_sasl_set_allow_insecure_mechs(self._sasl, insecure) - - allow_insecure_mechs = property(_get_allow_insecure_mechs, _set_allow_insecure_mechs, - doc=""" -Allow unencrypted cleartext passwords (PLAIN mech) -""") - - def done(self, outcome): - pn_sasl_done(self._sasl, outcome) - - def config_name(self, name): - pn_sasl_config_name(self._sasl, name) - - def config_path(self, path): - pn_sasl_config_path(self._sasl, path) - -class SSLException(TransportException): - pass - -class SSLUnavailable(SSLException): - pass - -class SSLDomain(object): - - MODE_CLIENT = PN_SSL_MODE_CLIENT - MODE_SERVER = PN_SSL_MODE_SERVER - VERIFY_PEER = PN_SSL_VERIFY_PEER - VERIFY_PEER_NAME = PN_SSL_VERIFY_PEER_NAME - ANONYMOUS_PEER = PN_SSL_ANONYMOUS_PEER - - def __init__(self, mode): - self._domain = pn_ssl_domain(mode) - if self._domain is None: - raise SSLUnavailable() - - def _check(self, err): - if err < 0: - exc = EXCEPTIONS.get(err, SSLException) - raise exc("SSL failure.") - else: - return err - - def set_credentials(self, cert_file, key_file, password): - return self._check( pn_ssl_domain_set_credentials(self._domain, - cert_file, key_file, - password) ) - def set_trusted_ca_db(self, certificate_db): - return self._check( pn_ssl_domain_set_trusted_ca_db(self._domain, - certificate_db) ) - def set_peer_authentication(self, verify_mode, trusted_CAs=None): - return self._check( pn_ssl_domain_set_peer_authentication(self._domain, - verify_mode, - trusted_CAs) ) - - def allow_unsecured_client(self): - return self._check( pn_ssl_domain_allow_unsecured_client(self._domain) ) - - def __del__(self): - pn_ssl_domain_free(self._domain) - -class SSL(object): - - @staticmethod - def present(): - return pn_ssl_present() - - def _check(self, err): - if err < 0: - exc = EXCEPTIONS.get(err, SSLException) - raise exc("SSL failure.") - else: - return err - - def __new__(cls, transport, domain, session_details=None): - """Enforce a singleton SSL object per Transport""" - if transport._ssl: - # unfortunately, we've combined the allocation and the configuration in a - # single step. So catch any attempt by the application to provide what - # may be a different configuration than the original (hack) - ssl = transport._ssl - if (domain and (ssl._domain is not domain) or - session_details and (ssl._session_details is not session_details)): - raise SSLException("Cannot re-configure existing SSL object!") - else: - obj = super(SSL, cls).__new__(cls) - obj._domain = domain - obj._session_details = session_details - session_id = None - if session_details: - session_id = session_details.get_session_id() - obj._ssl = pn_ssl( transport._impl ) - if obj._ssl is None: - raise SSLUnavailable() - if domain: - pn_ssl_init( obj._ssl, domain._domain, session_id ) - transport._ssl = obj - return transport._ssl - - def cipher_name(self): - rc, name = pn_ssl_get_cipher_name( self._ssl, 128 ) - if rc: - return name - return None - - def protocol_name(self): - rc, name = pn_ssl_get_protocol_name( self._ssl, 128 ) - if rc: - return name - return None - - SHA1 = PN_SSL_SHA1 - SHA256 = PN_SSL_SHA256 - SHA512 = PN_SSL_SHA512 - MD5 = PN_SSL_MD5 - - CERT_COUNTRY_NAME = PN_SSL_CERT_SUBJECT_COUNTRY_NAME - CERT_STATE_OR_PROVINCE = PN_SSL_CERT_SUBJECT_STATE_OR_PROVINCE - CERT_CITY_OR_LOCALITY = PN_SSL_CERT_SUBJECT_CITY_OR_LOCALITY - CERT_ORGANIZATION_NAME = PN_SSL_CERT_SUBJECT_ORGANIZATION_NAME - CERT_ORGANIZATION_UNIT = PN_SSL_CERT_SUBJECT_ORGANIZATION_UNIT - CERT_COMMON_NAME = PN_SSL_CERT_SUBJECT_COMMON_NAME - - def get_cert_subject_subfield(self, subfield_name): - subfield_value = pn_ssl_get_remote_subject_subfield(self._ssl, subfield_name) - return subfield_value - - def get_cert_subject(self): - subject = pn_ssl_get_remote_subject(self._ssl) - return subject - - def _get_cert_subject_unknown_subfield(self): - # Pass in an unhandled enum - return self.get_cert_subject_subfield(10) - - # Convenience functions for obtaining the subfields of the subject field. - def get_cert_common_name(self): - return self.get_cert_subject_subfield(SSL.CERT_COMMON_NAME) - - def get_cert_organization(self): - return self.get_cert_subject_subfield(SSL.CERT_ORGANIZATION_NAME) - - def get_cert_organization_unit(self): - return self.get_cert_subject_subfield(SSL.CERT_ORGANIZATION_UNIT) - - def get_cert_locality_or_city(self): - return self.get_cert_subject_subfield(SSL.CERT_CITY_OR_LOCALITY) - - def get_cert_country(self): - return self.get_cert_subject_subfield(SSL.CERT_COUNTRY_NAME) - - def get_cert_state_or_province(self): - return self.get_cert_subject_subfield(SSL.CERT_STATE_OR_PROVINCE) - - def get_cert_fingerprint(self, fingerprint_length, digest_name): - rc, fingerprint_str = pn_ssl_get_cert_fingerprint(self._ssl, fingerprint_length, digest_name) - if rc == PN_OK: - return fingerprint_str - return None - - # Convenience functions for obtaining fingerprint for specific hashing algorithms - def _get_cert_fingerprint_unknown_hash_alg(self): - return self.get_cert_fingerprint(41, 10) - - def get_cert_fingerprint_sha1(self): - return self.get_cert_fingerprint(41, SSL.SHA1) - - def get_cert_fingerprint_sha256(self): - # sha256 produces a fingerprint that is 64 characters long - return self.get_cert_fingerprint(65, SSL.SHA256) - - def get_cert_fingerprint_sha512(self): - # sha512 produces a fingerprint that is 128 characters long - return self.get_cert_fingerprint(129, SSL.SHA512) - - def get_cert_fingerprint_md5(self): - return self.get_cert_fingerprint(33, SSL.MD5) - - @property - def remote_subject(self): - return pn_ssl_get_remote_subject( self._ssl ) - - RESUME_UNKNOWN = PN_SSL_RESUME_UNKNOWN - RESUME_NEW = PN_SSL_RESUME_NEW - RESUME_REUSED = PN_SSL_RESUME_REUSED - - def resume_status(self): - return pn_ssl_resume_status( self._ssl ) - - def _set_peer_hostname(self, hostname): - self._check(pn_ssl_set_peer_hostname( self._ssl, unicode2utf8(hostname) )) - def _get_peer_hostname(self): - err, name = pn_ssl_get_peer_hostname( self._ssl, 1024 ) - self._check(err) - return utf82unicode(name) - peer_hostname = property(_get_peer_hostname, _set_peer_hostname, - doc=""" -Manage the expected name of the remote peer. Used to authenticate the remote. -""") - - -class SSLSessionDetails(object): - """ Unique identifier for the SSL session. Used to resume previous session on a new - SSL connection. - """ - - def __init__(self, session_id): - self._session_id = session_id - - def get_session_id(self): - return self._session_id - - -wrappers = { - "pn_void": lambda x: pn_void2py(x), - "pn_pyref": lambda x: pn_void2py(x), - "pn_connection": lambda x: Connection.wrap(pn_cast_pn_connection(x)), - "pn_session": lambda x: Session.wrap(pn_cast_pn_session(x)), - "pn_link": lambda x: Link.wrap(pn_cast_pn_link(x)), - "pn_delivery": lambda x: Delivery.wrap(pn_cast_pn_delivery(x)), - "pn_transport": lambda x: Transport.wrap(pn_cast_pn_transport(x)), - "pn_selectable": lambda x: Selectable.wrap(pn_cast_pn_selectable(x)) -} - -class Collector: - - def __init__(self): - self._impl = pn_collector() - - def put(self, obj, etype): - pn_collector_put(self._impl, PN_PYREF, pn_py2void(obj), etype.number) - - def peek(self): - return Event.wrap(pn_collector_peek(self._impl)) - - def pop(self): - ev = self.peek() - pn_collector_pop(self._impl) - - def __del__(self): - pn_collector_free(self._impl) - del self._impl - -if "TypeExtender" not in globals(): - class TypeExtender: - def __init__(self, number): - self.number = number - def next(self): - try: - return self.number - finally: - self.number += 1 - -class EventType(object): - - _lock = threading.Lock() - _extended = TypeExtender(10000) - TYPES = {} - - def __init__(self, name=None, number=None, method=None): - if name is None and number is None: - raise TypeError("extended events require a name") - try: - self._lock.acquire() - if name is None: - name = pn_event_type_name(number) - - if number is None: - number = self._extended.next() - - if method is None: - method = "on_%s" % name - - self.name = name - self.number = number - self.method = method - - self.TYPES[number] = self - finally: - self._lock.release() - - def __repr__(self): - return self.name - -def dispatch(handler, method, *args): - m = getattr(handler, method, None) - if m: - return m(*args) - elif hasattr(handler, "on_unhandled"): - return handler.on_unhandled(method, *args) - -class EventBase(object): - - def __init__(self, clazz, context, type): - self.clazz = clazz - self.context = context - self.type = type - - def dispatch(self, handler): - return dispatch(handler, self.type.method, self) - -def _none(x): return None - -DELEGATED = Constant("DELEGATED") - -def _core(number, method): - return EventType(number=number, method=method) - -class Event(Wrapper, EventBase): - - REACTOR_INIT = _core(PN_REACTOR_INIT, "on_reactor_init") - REACTOR_QUIESCED = _core(PN_REACTOR_QUIESCED, "on_reactor_quiesced") - REACTOR_FINAL = _core(PN_REACTOR_FINAL, "on_reactor_final") - - TIMER_TASK = _core(PN_TIMER_TASK, "on_timer_task") - - CONNECTION_INIT = _core(PN_CONNECTION_INIT, "on_connection_init") - CONNECTION_BOUND = _core(PN_CONNECTION_BOUND, "on_connection_bound") - CONNECTION_UNBOUND = _core(PN_CONNECTION_UNBOUND, "on_connection_unbound") - CONNECTION_LOCAL_OPEN = _core(PN_CONNECTION_LOCAL_OPEN, "on_connection_local_open") - CONNECTION_LOCAL_CLOSE = _core(PN_CONNECTION_LOCAL_CLOSE, "on_connection_local_close") - CONNECTION_REMOTE_OPEN = _core(PN_CONNECTION_REMOTE_OPEN, "on_connection_remote_open") - CONNECTION_REMOTE_CLOSE = _core(PN_CONNECTION_REMOTE_CLOSE, "on_connection_remote_close") - CONNECTION_FINAL = _core(PN_CONNECTION_FINAL, "on_connection_final") - - SESSION_INIT = _core(PN_SESSION_INIT, "on_session_init") - SESSION_LOCAL_OPEN = _core(PN_SESSION_LOCAL_OPEN, "on_session_local_open") - SESSION_LOCAL_CLOSE = _core(PN_SESSION_LOCAL_CLOSE, "on_session_local_close") - SESSION_REMOTE_OPEN = _core(PN_SESSION_REMOTE_OPEN, "on_session_remote_open") - SESSION_REMOTE_CLOSE = _core(PN_SESSION_REMOTE_CLOSE, "on_session_remote_close") - SESSION_FINAL = _core(PN_SESSION_FINAL, "on_session_final") - - LINK_INIT = _core(PN_LINK_INIT, "on_link_init") - LINK_LOCAL_OPEN = _core(PN_LINK_LOCAL_OPEN, "on_link_local_open") - LINK_LOCAL_CLOSE = _core(PN_LINK_LOCAL_CLOSE, "on_link_local_close") - LINK_LOCAL_DETACH = _core(PN_LINK_LOCAL_DETACH, "on_link_local_detach") - LINK_REMOTE_OPEN = _core(PN_LINK_REMOTE_OPEN, "on_link_remote_open") - LINK_REMOTE_CLOSE = _core(PN_LINK_REMOTE_CLOSE, "on_link_remote_close") - LINK_REMOTE_DETACH = _core(PN_LINK_REMOTE_DETACH, "on_link_remote_detach") - LINK_FLOW = _core(PN_LINK_FLOW, "on_link_flow") - LINK_FINAL = _core(PN_LINK_FINAL, "on_link_final") - - DELIVERY = _core(PN_DELIVERY, "on_delivery") - - TRANSPORT = _core(PN_TRANSPORT, "on_transport") - TRANSPORT_ERROR = _core(PN_TRANSPORT_ERROR, "on_transport_error") - TRANSPORT_HEAD_CLOSED = _core(PN_TRANSPORT_HEAD_CLOSED, "on_transport_head_closed") - TRANSPORT_TAIL_CLOSED = _core(PN_TRANSPORT_TAIL_CLOSED, "on_transport_tail_closed") - TRANSPORT_CLOSED = _core(PN_TRANSPORT_CLOSED, "on_transport_closed") - - SELECTABLE_INIT = _core(PN_SELECTABLE_INIT, "on_selectable_init") - SELECTABLE_UPDATED = _core(PN_SELECTABLE_UPDATED, "on_selectable_updated") - SELECTABLE_READABLE = _core(PN_SELECTABLE_READABLE, "on_selectable_readable") - SELECTABLE_WRITABLE = _core(PN_SELECTABLE_WRITABLE, "on_selectable_writable") - SELECTABLE_EXPIRED = _core(PN_SELECTABLE_EXPIRED, "on_selectable_expired") - SELECTABLE_ERROR = _core(PN_SELECTABLE_ERROR, "on_selectable_error") - SELECTABLE_FINAL = _core(PN_SELECTABLE_FINAL, "on_selectable_final") - - @staticmethod - def wrap(impl, number=None): - if impl is None: - return None - - if number is None: - number = pn_event_type(impl) - - event = Event(impl, number) - - # check for an application defined ApplicationEvent and return that. This - # avoids an expensive wrap operation invoked by event.context - if pn_event_class(impl) == PN_PYREF and \ - isinstance(event.context, EventBase): - return event.context - else: - return event - - def __init__(self, impl, number): - Wrapper.__init__(self, impl, pn_event_attachments) - self.__dict__["type"] = EventType.TYPES[number] - - def _init(self): - pass - - def copy(self): - copy = pn_event_copy(self._impl) - return Event.wrap(copy) - - @property - def clazz(self): - cls = pn_event_class(self._impl) - if cls: - return pn_class_name(cls) - else: - return None - - @property - def root(self): - return WrappedHandler.wrap(pn_event_root(self._impl)) - - @property - def context(self): - """Returns the context object associated with the event. The type of this depend on the type of event.""" - return wrappers[self.clazz](pn_event_context(self._impl)) - - def dispatch(self, handler, type=None): - type = type or self.type - if isinstance(handler, WrappedHandler): - pn_handler_dispatch(handler._impl, self._impl, type.number) - else: - result = dispatch(handler, type.method, self) - if result != DELEGATED and hasattr(handler, "handlers"): - for h in handler.handlers: - self.dispatch(h, type) - - - @property - def reactor(self): - """Returns the reactor associated with the event.""" - return wrappers.get("pn_reactor", _none)(pn_event_reactor(self._impl)) - - def __getattr__(self, name): - r = self.reactor - if r and hasattr(r, 'subclass') and r.subclass.__name__.lower() == name: - return r - else: - return super(Event, self).__getattr__(name) - - @property - def transport(self): - """Returns the transport associated with the event, or null if none is associated with it.""" - return Transport.wrap(pn_event_transport(self._impl)) - - @property - def connection(self): - """Returns the connection associated with the event, or null if none is associated with it.""" - return Connection.wrap(pn_event_connection(self._impl)) - - @property - def session(self): - """Returns the session associated with the event, or null if none is associated with it.""" - return Session.wrap(pn_event_session(self._impl)) - - @property - def link(self): - """Returns the link associated with the event, or null if none is associated with it.""" - return Link.wrap(pn_event_link(self._impl)) - - @property - def sender(self): - """Returns the sender link associated with the event, or null if - none is associated with it. This is essentially an alias for - link(), that does an additional checkon the type of the - link.""" - l = self.link - if l and l.is_sender: - return l - else: - return None - - @property - def receiver(self): - """Returns the receiver link associated with the event, or null if - none is associated with it. This is essentially an alias for - link(), that does an additional checkon the type of the link.""" - l = self.link - if l and l.is_receiver: - return l - else: - return None - - @property - def delivery(self): - """Returns the delivery associated with the event, or null if none is associated with it.""" - return Delivery.wrap(pn_event_delivery(self._impl)) - - def __repr__(self): - return "%s(%s)" % (self.type, self.context) - -class LazyHandlers(object): - def __get__(self, obj, clazz): - if obj is None: - return self - ret = [] - obj.__dict__['handlers'] = ret - return ret - -class Handler(object): - handlers = LazyHandlers() - - def on_unhandled(self, method, *args): - pass - -class _cadapter: - - def __init__(self, handler, on_error=None): - self.handler = handler - self.on_error = on_error - - def dispatch(self, cevent, ctype): - ev = Event.wrap(cevent, ctype) - ev.dispatch(self.handler) - - def exception(self, exc, val, tb): - if self.on_error is None: - _compat.raise_(exc, val, tb) - else: - self.on_error((exc, val, tb)) - -class WrappedHandlersChildSurrogate: - def __init__(self, delegate): - self.handlers = [] - self.delegate = weakref.ref(delegate) - - def on_unhandled(self, method, event): - delegate = self.delegate() - if delegate: - dispatch(delegate, method, event) - - -class WrappedHandlersProperty(object): - def __get__(self, obj, clazz): - if obj is None: - return None - return self.surrogate(obj).handlers - - def __set__(self, obj, value): - self.surrogate(obj).handlers = value - - def surrogate(self, obj): - key = "_surrogate" - objdict = obj.__dict__ - surrogate = objdict.get(key, None) - if surrogate is None: - objdict[key] = surrogate = WrappedHandlersChildSurrogate(obj) - obj.add(surrogate) - return surrogate - -class WrappedHandler(Wrapper): - - handlers = WrappedHandlersProperty() - - @classmethod - def wrap(cls, impl, on_error=None): - if impl is None: - return None - else: - handler = cls(impl) - handler.__dict__["on_error"] = on_error - return handler - - def __init__(self, impl_or_constructor): - Wrapper.__init__(self, impl_or_constructor) - if list(self.__class__.__mro__).index(WrappedHandler) > 1: - # instantiate the surrogate - self.handlers.extend([]) - - def _on_error(self, info): - on_error = getattr(self, "on_error", None) - if on_error is None: - _compat.raise_(info[0], info[1], info[2]) - else: - on_error(info) - - def add(self, handler, on_error=None): - if handler is None: return - if on_error is None: on_error = self._on_error - impl = _chandler(handler, on_error) - pn_handler_add(self._impl, impl) - pn_decref(impl) - - def clear(self): - pn_handler_clear(self._impl) - -def _chandler(obj, on_error=None): - if obj is None: - return None - elif isinstance(obj, WrappedHandler): - impl = obj._impl - pn_incref(impl) - return impl - else: - return pn_pyhandler(_cadapter(obj, on_error)) - -class Url(object): - """ - Simple URL parser/constructor, handles URLs of the form: - - ://:@:/ - - All components can be None if not specified in the URL string. - - The port can be specified as a service name, e.g. 'amqp' in the - URL string but Url.port always gives the integer value. - - Warning: The placement of user and password in URLs is not - recommended. It can result in credentials leaking out in program - logs. Use connection configuration attributes instead. - - @ivar scheme: Url scheme e.g. 'amqp' or 'amqps' - @ivar user: Username - @ivar password: Password - @ivar host: Host name, ipv6 literal or ipv4 dotted quad. - @ivar port: Integer port. - @ivar host_port: Returns host:port - """ - - AMQPS = "amqps" - AMQP = "amqp" - - class Port(int): - """An integer port number that can be constructed from a service name string""" - - def __new__(cls, value): - """@param value: integer port number or string service name.""" - port = super(Url.Port, cls).__new__(cls, cls._port_int(value)) - setattr(port, 'name', str(value)) - return port - - def __eq__(self, x): return str(self) == x or int(self) == x - def __ne__(self, x): return not self == x - def __str__(self): return str(self.name) - - @staticmethod - def _port_int(value): - """Convert service, an integer or a service name, into an integer port number.""" - try: - return int(value) - except ValueError: - try: - return socket.getservbyname(value) - except socket.error: - # Not every system has amqp/amqps defined as a service - if value == Url.AMQPS: return 5671 - elif value == Url.AMQP: return 5672 - else: - raise ValueError("Not a valid port number or service name: '%s'" % value) - - def __init__(self, url=None, defaults=True, **kwargs): - """ - @param url: URL string to parse. - @param defaults: If true, fill in missing default values in the URL. - If false, you can fill them in later by calling self.defaults() - @param kwargs: scheme, user, password, host, port, path. - If specified, replaces corresponding part in url string. - """ - if url: - self._url = pn_url_parse(unicode2utf8(str(url))) - if not self._url: raise ValueError("Invalid URL '%s'" % url) - else: - self._url = pn_url() - for k in kwargs: # Let kwargs override values parsed from url - getattr(self, k) # Check for invalid kwargs - setattr(self, k, kwargs[k]) - if defaults: self.defaults() - - class PartDescriptor(object): - def __init__(self, part): - self.getter = globals()["pn_url_get_%s" % part] - self.setter = globals()["pn_url_set_%s" % part] - def __get__(self, obj, type=None): return self.getter(obj._url) - def __set__(self, obj, value): return self.setter(obj._url, str(value)) - - scheme = PartDescriptor('scheme') - username = PartDescriptor('username') - password = PartDescriptor('password') - host = PartDescriptor('host') - path = PartDescriptor('path') - - def _get_port(self): - portstr = pn_url_get_port(self._url) - return portstr and Url.Port(portstr) - - def _set_port(self, value): - if value is None: pn_url_set_port(self._url, None) - else: pn_url_set_port(self._url, str(Url.Port(value))) - - port = property(_get_port, _set_port) - - def __str__(self): return pn_url_str(self._url) - - def __repr__(self): - return "Url(%s://%s/%s)" % (self.scheme, self.host, self.path) - - def __eq__(self, x): return str(self) == str(x) - def __ne__(self, x): return not self == x - - def __del__(self): - pn_url_free(self._url); - del self._url - - def defaults(self): - """ - Fill in missing values (scheme, host or port) with defaults - @return: self - """ - self.scheme = self.scheme or self.AMQP - self.host = self.host or '0.0.0.0' - self.port = self.port or self.Port(self.scheme) - return self - -__all__ = [ - "API_LANGUAGE", - "IMPLEMENTATION_LANGUAGE", - "ABORTED", - "ACCEPTED", - "PENDING", - "REJECTED", - "RELEASED", - "MODIFIED", - "SETTLED", - "UNDESCRIBED", - "Array", - "Collector", - "Condition", - "Connection", - "Data", - "Delivery", - "Disposition", - "Described", - "Endpoint", - "Event", - "EventType", - "Handler", - "Link", - "Message", - "MessageException", - "ProtonException", - "VERSION_MAJOR", - "VERSION_MINOR", - "Receiver", - "SASL", - "Sender", - "Session", - "SessionException", - "SSL", - "SSLDomain", - "SSLSessionDetails", - "SSLUnavailable", - "SSLException", - "Terminus", - "Timeout", - "Interrupt", - "Transport", - "TransportException", - "Url", - "char", - "dispatch", - "symbol", - "timestamp", - "ulong", - "byte", - "short", - "int32", - "ubyte", - "ushort", - "uint", - "float32", - "decimal32", - "decimal64", - "decimal128" - ] +def generate_uuid(): + import uuid + return uuid.uuid4() diff --git a/python/proton/_common.py b/python/proton/_common.py new file mode 100644 index 0000000000..3715c6a715 --- /dev/null +++ b/python/proton/_common.py @@ -0,0 +1,91 @@ +# +# 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. +# + + +# +# 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 + + +def isinteger(value): + return isinstance(value, (int, long)) + + +def isstring(value): + return isinstance(value, (str, unicode)) + + +class Constant(object): + + def __init__(self, name): + self.name = name + + def __repr__(self): + return self.name + + +def secs2millis(secs): + return long(secs * 1000) + + +def millis2secs(millis): + return float(millis) / 1000.0 + + +def unicode2utf8(string): + """Some Proton APIs expect a null terminated string. Convert python text + types to UTF8 to avoid zero bytes introduced by other multi-byte encodings. + This method will throw if the string cannot be converted. + """ + if string is None: + return None + elif isinstance(string, str): + # Must be py2 or py3 str + # The swig binding converts py3 str -> utf8 char* and back sutomatically + return string + elif isinstance(string, unicode): + # This must be python2 unicode as we already detected py3 str above + return string.encode('utf-8') + # Anything else illegal - specifically python3 bytes + raise TypeError("Unrecognized string type: %r (%s)" % (string, type(string))) + + +def utf82unicode(string): + """Convert C strings returned from proton-c into python unicode""" + if string is None: + return None + elif isinstance(string, unicode): + # py2 unicode, py3 str (via hack definition) + return string + elif isinstance(string, bytes): + # py2 str (via hack definition), py3 bytes + return string.decode('utf8') + raise TypeError("Unrecognized string type") diff --git a/python/proton/_compat.py b/python/proton/_compat.py index afd82e3e6f..eae4c840d6 100644 --- a/python/proton/_compat.py +++ b/python/proton/_compat.py @@ -32,8 +32,6 @@ PY3 = sys.version_info[0] == 3 if PY3: - string_types = (str,) - def raise_(t, v=None, tb=None): """Mimic the old 2.x raise behavior: Raise an exception of type t with value v using optional traceback tb @@ -45,23 +43,22 @@ def raise_(t, v=None, tb=None): else: raise v.with_traceback(tb) + def iteritems(d, **kw): return iter(d.items(**kw)) + unichr = chr else: - # includes both unicode and non-unicode strings: - string_types = (basestring,) - # the raise syntax will cause a parse error in Py3, so 'sneak' in a # definition that won't cause the parser to barf - exec("""def raise_(t, v=None, tb=None): + exec ("""def raise_(t, v=None, tb=None): raise t, v, tb """) + def iteritems(d, **kw): return d.iteritems(**kw) - unichr = unichr -__all__ = [ 'PY3', 'queue', 'string_types', 'raise_', 'iteritems', 'unichr'] \ No newline at end of file + unichr = unichr diff --git a/python/proton/_condition.py b/python/proton/_condition.py new file mode 100644 index 0000000000..e5dbde9471 --- /dev/null +++ b/python/proton/_condition.py @@ -0,0 +1,63 @@ +# +# 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 + +from cproton import pn_condition_clear, pn_condition_set_name, pn_condition_set_description, pn_condition_info, \ + pn_condition_is_set, pn_condition_get_name, pn_condition_get_description + +from ._data import Data, dat2obj + + +class Condition: + + def __init__(self, name, description=None, info=None): + self.name = name + self.description = description + self.info = info + + def __repr__(self): + return "Condition(%s)" % ", ".join([repr(x) for x in + (self.name, self.description, self.info) + if x]) + + def __eq__(self, o): + if not isinstance(o, Condition): return False + return self.name == o.name and \ + self.description == o.description and \ + self.info == o.info + + +def obj2cond(obj, cond): + pn_condition_clear(cond) + if obj: + pn_condition_set_name(cond, str(obj.name)) + pn_condition_set_description(cond, obj.description) + info = Data(pn_condition_info(cond)) + if obj.info: + info.put_object(obj.info) + + +def cond2obj(cond): + if pn_condition_is_set(cond): + return Condition(pn_condition_get_name(cond), + pn_condition_get_description(cond), + dat2obj(pn_condition_info(cond))) + else: + return None diff --git a/python/proton/_data.py b/python/proton/_data.py new file mode 100644 index 0000000000..f4ad381b89 --- /dev/null +++ b/python/proton/_data.py @@ -0,0 +1,1129 @@ +# +# 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_TIMESTAMP, PN_FLOAT, PN_DESCRIBED, PN_DECIMAL64, PN_UBYTE, PN_UUID, PN_NULL, PN_BINARY, \ + PN_LIST, PN_OVERFLOW, PN_MAP, PN_LONG, PN_SHORT, PN_CHAR, PN_UINT, PN_ULONG, PN_STRING, PN_USHORT, PN_DOUBLE, \ + PN_BYTE, PN_DECIMAL32, PN_DECIMAL128, PN_ARRAY, PN_SYMBOL, PN_BOOL, PN_INT, \ + pn_data_get_binary, pn_data_get_decimal64, pn_data_put_symbol, pn_data_put_float, \ + pn_data_is_array_described, pn_data_exit, pn_data_put_uint, pn_data_put_decimal128, \ + pn_data_lookup, pn_data_put_char, pn_data_encoded_size, pn_data_get_bool, \ + pn_data_get_short, pn_data_prev, pn_data_type, pn_data_widen, pn_data_put_decimal64, \ + pn_data_put_string, pn_data_get_array, pn_data_put_ulong, pn_data_get_byte, pn_data_get_symbol, pn_data_encode, \ + pn_data_rewind, pn_data_put_bool, pn_data_is_null, pn_data_error, \ + pn_data_put_double, pn_data_copy, pn_data_put_int, pn_data_get_ubyte, pn_data_free, pn_data_clear, \ + pn_data_get_double, pn_data_put_byte, pn_data_put_uuid, pn_data_put_ushort, pn_data_is_described, \ + pn_data_get_float, pn_data_get_uint, pn_data_put_described, pn_data_get_decimal128, pn_data, \ + pn_data_get_array_type, pn_data_put_map, pn_data_put_list, pn_data_get_string, pn_data_get_char, \ + pn_data_put_decimal32, pn_data_enter, pn_data_put_short, pn_data_put_timestamp, \ + pn_data_get_long, pn_data_get_map, pn_data_narrow, pn_data_put_array, pn_data_get_ushort, \ + pn_data_get_int, pn_data_get_list, pn_data_get_ulong, pn_data_put_ubyte, \ + pn_data_format, pn_data_dump, pn_data_get_uuid, pn_data_get_decimal32, \ + pn_data_put_binary, pn_data_get_timestamp, pn_data_decode, pn_data_next, pn_data_put_null, pn_data_put_long, \ + pn_error_text + +from ._common import Constant +from ._exceptions import EXCEPTIONS, DataException + +from . import _compat + +# +# 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): + + def __repr__(self): + return "ulong(%s)" % long.__repr__(self) + + +class timestamp(long): + + def __repr__(self): + return "timestamp(%s)" % long.__repr__(self) + + +class symbol(unicode): + + def __repr__(self): + return "symbol(%s)" % unicode.__repr__(self) + + +class char(unicode): + + def __repr__(self): + return "char(%s)" % unicode.__repr__(self) + + +class byte(int): + + def __repr__(self): + return "byte(%s)" % int.__repr__(self) + + +class short(int): + + def __repr__(self): + return "short(%s)" % int.__repr__(self) + + +class int32(int): + + def __repr__(self): + return "int32(%s)" % int.__repr__(self) + + +class ubyte(int): + + def __repr__(self): + return "ubyte(%s)" % int.__repr__(self) + + +class ushort(int): + + def __repr__(self): + return "ushort(%s)" % int.__repr__(self) + + +class uint(long): + + def __repr__(self): + return "uint(%s)" % long.__repr__(self) + + +class float32(float): + + def __repr__(self): + return "float32(%s)" % float.__repr__(self) + + +class decimal32(int): + + def __repr__(self): + return "decimal32(%s)" % int.__repr__(self) + + +class decimal64(long): + + def __repr__(self): + return "decimal64(%s)" % long.__repr__(self) + + +class decimal128(bytes): + + def __repr__(self): + return "decimal128(%s)" % bytes.__repr__(self) + + +class Described(object): + + 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): + + 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 + + +class Data: + """ + The L{Data} class provides an interface for decoding, extracting, + creating, and encoding arbitrary AMQP data. A L{Data} object + contains a tree of AMQP values. Leaf nodes in this tree correspond + to scalars in the AMQP type system such as L{ints} or + L{strings}. Non-leaf nodes in this tree correspond to + compound values in the AMQP type system such as L{lists}, + L{maps}, L{arrays}, or L{described values}. + The root node of the tree is the L{Data} object itself and can have + an arbitrary number of children. + + A L{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 L{next}, L{prev}, + L{enter}, and L{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 I{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: + + - L{NULL} + - L{BOOL} + - L{UBYTE} + - L{USHORT} + - L{SHORT} + - L{UINT} + - L{INT} + - L{ULONG} + - L{LONG} + - L{FLOAT} + - L{DOUBLE} + - L{BINARY} + - L{STRING} + - L{SYMBOL} + + The following types of compound values are supported: + + - L{DESCRIBED} + - L{ARRAY} + - L{LIST} + - L{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" + } + + @classmethod + def type_name(type): + return Data.type_names[type] + + 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. + """ + 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. + """ + 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 next() advances to the first child. + """ + 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 pn_data_exit(self._data) + + def lookup(self, name): + return pn_data_lookup(self._data, name) + + def narrow(self): + pn_data_narrow(self._data) + + def widen(self): + pn_data_widen(self._data) + + def type(self): + """ + Returns the type of the current node. + """ + 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 pn_data_encoded_size(self._data) + + def encode(self): + """ + Returns a representation of the data encoded in AMQP format. + """ + 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 + """ + 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() + """ + 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() + """ + 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 + """ + 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() + """ + self._check(pn_data_put_described(self._data)) + + def put_null(self): + """ + Puts a null value. + """ + self._check(pn_data_put_null(self._data)) + + def put_bool(self, b): + """ + Puts a boolean value. + + @param b: a boolean value + """ + self._check(pn_data_put_bool(self._data, b)) + + def put_ubyte(self, ub): + """ + Puts an unsigned byte value. + + @param ub: an integral value + """ + self._check(pn_data_put_ubyte(self._data, ub)) + + def put_byte(self, b): + """ + Puts a signed byte value. + + @param b: an integral value + """ + self._check(pn_data_put_byte(self._data, b)) + + def put_ushort(self, us): + """ + Puts an unsigned short value. + + @param us: an integral value. + """ + self._check(pn_data_put_ushort(self._data, us)) + + def put_short(self, s): + """ + Puts a signed short value. + + @param s: an integral value + """ + self._check(pn_data_put_short(self._data, s)) + + def put_uint(self, ui): + """ + Puts an unsigned int value. + + @param ui: an integral value + """ + self._check(pn_data_put_uint(self._data, ui)) + + def put_int(self, i): + """ + Puts a signed int value. + + @param i: an integral value + """ + self._check(pn_data_put_int(self._data, i)) + + def put_char(self, c): + """ + Puts a char value. + + @param c: a single character + """ + 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 + """ + self._check(pn_data_put_ulong(self._data, ul)) + + def put_long(self, l): + """ + Puts a signed long value. + + @param l: an integral value + """ + self._check(pn_data_put_long(self._data, l)) + + def put_timestamp(self, t): + """ + Puts a timestamp value. + + @param t: an integral value + """ + self._check(pn_data_put_timestamp(self._data, t)) + + def put_float(self, f): + """ + Puts a float value. + + @param f: a floating point value + """ + self._check(pn_data_put_float(self._data, f)) + + def put_double(self, d): + """ + Puts a double value. + + @param d: a floating point value. + """ + self._check(pn_data_put_double(self._data, d)) + + def put_decimal32(self, d): + """ + Puts a decimal32 value. + + @param d: a decimal32 value + """ + self._check(pn_data_put_decimal32(self._data, d)) + + def put_decimal64(self, d): + """ + Puts a decimal64 value. + + @param d: a decimal64 value + """ + self._check(pn_data_put_decimal64(self._data, d)) + + def put_decimal128(self, d): + """ + Puts a decimal128 value. + + @param d: a decimal128 value + """ + self._check(pn_data_put_decimal128(self._data, d)) + + def put_uuid(self, u): + """ + Puts a UUID value. + + @param u: a uuid value + """ + self._check(pn_data_put_uuid(self._data, u.bytes)) + + def put_binary(self, b): + """ + Puts a binary value. + + @type b: binary + @param b: a binary value + """ + self._check(pn_data_put_binary(self._data, b)) + + def put_memoryview(self, mv): + """Put a python memoryview object as an AMQP binary value""" + self.put_binary(mv.tobytes()) + + def put_buffer(self, buff): + """Put a python buffer object as an AMQP binary value""" + self.put_binary(bytes(buff)) + + def put_string(self, s): + """ + Puts a unicode value. + + @type s: unicode + @param s: a unicode value + """ + self._check(pn_data_put_string(self._data, s.encode("utf8"))) + + def put_symbol(self, s): + """ + Puts a symbolic value. + + @type s: string + @param s: the symbol name + """ + 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 zero. 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 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 zero. 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 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 (0, False, 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() + """ + 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 pn_data_is_described(self._data) + + def is_null(self): + """ + Checks if the current node is a null. + """ + return pn_data_is_null(self._data) + + def get_bool(self): + """ + If the current node is a boolean, returns its value, returns False + otherwise. + """ + return pn_data_get_bool(self._data) + + def get_ubyte(self): + """ + If the current node is an unsigned byte, returns its value, + returns 0 otherwise. + """ + return ubyte(pn_data_get_ubyte(self._data)) + + def get_byte(self): + """ + If the current node is a signed byte, returns its value, returns 0 + otherwise. + """ + return byte(pn_data_get_byte(self._data)) + + def get_ushort(self): + """ + If the current node is an unsigned short, returns its value, + returns 0 otherwise. + """ + return ushort(pn_data_get_ushort(self._data)) + + def get_short(self): + """ + If the current node is a signed short, returns its value, returns + 0 otherwise. + """ + return short(pn_data_get_short(self._data)) + + def get_uint(self): + """ + If the current node is an unsigned int, returns its value, returns + 0 otherwise. + """ + return uint(pn_data_get_uint(self._data)) + + def get_int(self): + """ + If the current node is a signed int, returns its value, returns 0 + otherwise. + """ + return int32(pn_data_get_int(self._data)) + + def get_char(self): + """ + If the current node is a char, returns its value, returns 0 + otherwise. + """ + return char(_compat.unichr(pn_data_get_char(self._data))) + + def get_ulong(self): + """ + If the current node is an unsigned long, returns its value, + returns 0 otherwise. + """ + return ulong(pn_data_get_ulong(self._data)) + + def get_long(self): + """ + If the current node is an signed long, returns its value, returns + 0 otherwise. + """ + return long(pn_data_get_long(self._data)) + + def get_timestamp(self): + """ + If the current node is a timestamp, returns its value, returns 0 + otherwise. + """ + return timestamp(pn_data_get_timestamp(self._data)) + + def get_float(self): + """ + If the current node is a float, returns its value, raises 0 + otherwise. + """ + return float32(pn_data_get_float(self._data)) + + def get_double(self): + """ + If the current node is a double, returns its value, returns 0 + otherwise. + """ + return pn_data_get_double(self._data) + + # XXX: need to convert + def get_decimal32(self): + """ + If the current node is a decimal32, returns its value, returns 0 + otherwise. + """ + return decimal32(pn_data_get_decimal32(self._data)) + + # XXX: need to convert + def get_decimal64(self): + """ + If the current node is a decimal64, returns its value, returns 0 + otherwise. + """ + return decimal64(pn_data_get_decimal64(self._data)) + + # XXX: need to convert + def get_decimal128(self): + """ + If the current node is a decimal128, returns its value, returns 0 + otherwise. + """ + return decimal128(pn_data_get_decimal128(self._data)) + + def get_uuid(self): + """ + If the current node is a UUID, returns its value, returns None + otherwise. + """ + 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): + """ + If the current node is binary, returns its value, returns "" + otherwise. + """ + return pn_data_get_binary(self._data) + + def get_string(self): + """ + If the current node is a string, returns its value, returns "" + otherwise. + """ + return pn_data_get_string(self._data).decode("utf8") + + def get_symbol(self): + """ + If the current node is a symbol, returns its value, returns "" + otherwise. + """ + return symbol(pn_data_get_symbol(self._data).decode('ascii')) + + def copy(self, src): + self._check(pn_data_copy(self._data, src._data)) + + def format(self): + 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): + pn_data_dump(self._data) + + def put_dict(self, d): + 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): + 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): + self.put_list() + self.enter() + try: + for o in s: + self.put_object(o) + finally: + self.exit() + + def get_sequence(self): + if self.enter(): + try: + result = [] + while self.next(): + result.append(self.get_object()) + finally: + self.exit() + return result + + def get_py_described(self): + 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): + self.put_described() + self.enter() + try: + self.put_object(d.descriptor) + self.put_object(d.value) + finally: + self.exit() + + def get_py_array(self): + """ + If the current node is an array, return an Array object + representing the array and its contents. Otherwise return None. + This is a convenience wrapper around get_array, enter, etc. + """ + + 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): + 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 + } + # 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) diff --git a/python/proton/_delivery.py b/python/proton/_delivery.py new file mode 100644 index 0000000000..e60945162b --- /dev/null +++ b/python/proton/_delivery.py @@ -0,0 +1,293 @@ +# +# 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 + +from cproton import PN_REJECTED, PN_RELEASED, PN_MODIFIED, PN_RECEIVED, PN_ACCEPTED, \ + pn_disposition_is_undeliverable, pn_disposition_set_section_number, pn_disposition_get_section_number, \ + pn_disposition_set_undeliverable, pn_disposition_set_failed, pn_disposition_condition, \ + pn_disposition_set_section_offset, pn_disposition_data, pn_disposition_get_section_offset, \ + pn_disposition_is_failed, pn_disposition_annotations, \ + pn_delivery_partial, pn_delivery_aborted, pn_disposition_type, pn_delivery_pending, pn_delivery_updated, \ + pn_delivery_readable, pn_delivery_abort, pn_delivery_remote, pn_delivery_tag, pn_delivery_link, pn_delivery_local, \ + pn_delivery_update, pn_delivery_attachments, pn_delivery_local_state, pn_delivery_settled, pn_delivery_settle, \ + pn_delivery_writable, pn_delivery_remote_state, \ + pn_work_next + +from ._condition import cond2obj, obj2cond +from ._data import dat2obj, obj2dat +from ._wrapper import Wrapper + + +class NamedInt(int): + values = {} # type: Dict[int, str] + + def __new__(cls, i, name): + ni = super(NamedInt, cls).__new__(cls, i) + cls.values[i] = ni + return ni + + def __init__(self, i, name): + self.name = name + + def __repr__(self): + return self.name + + def __str__(self): + return self.name + + @classmethod + def get(cls, i): + return cls.values.get(i, i) + + +class DispositionType(NamedInt): + values = {} + + +class Disposition(object): + RECEIVED = DispositionType(PN_RECEIVED, "RECEIVED") + ACCEPTED = DispositionType(PN_ACCEPTED, "ACCEPTED") + REJECTED = DispositionType(PN_REJECTED, "REJECTED") + RELEASED = DispositionType(PN_RELEASED, "RELEASED") + MODIFIED = DispositionType(PN_MODIFIED, "MODIFIED") + + def __init__(self, impl, local): + self._impl = impl + self.local = local + self._data = None + self._condition = None + self._annotations = None + + @property + def type(self): + return DispositionType.get(pn_disposition_type(self._impl)) + + def _get_section_number(self): + return pn_disposition_get_section_number(self._impl) + + def _set_section_number(self, n): + pn_disposition_set_section_number(self._impl, n) + + section_number = property(_get_section_number, _set_section_number) + + def _get_section_offset(self): + return pn_disposition_get_section_offset(self._impl) + + def _set_section_offset(self, n): + pn_disposition_set_section_offset(self._impl, n) + + section_offset = property(_get_section_offset, _set_section_offset) + + def _get_failed(self): + return pn_disposition_is_failed(self._impl) + + def _set_failed(self, b): + pn_disposition_set_failed(self._impl, b) + + failed = property(_get_failed, _set_failed) + + def _get_undeliverable(self): + return pn_disposition_is_undeliverable(self._impl) + + def _set_undeliverable(self, b): + pn_disposition_set_undeliverable(self._impl, b) + + undeliverable = property(_get_undeliverable, _set_undeliverable) + + def _get_data(self): + if self.local: + return self._data + else: + return dat2obj(pn_disposition_data(self._impl)) + + def _set_data(self, obj): + if self.local: + self._data = obj + else: + raise AttributeError("data attribute is read-only") + + data = property(_get_data, _set_data) + + def _get_annotations(self): + if self.local: + return self._annotations + else: + return dat2obj(pn_disposition_annotations(self._impl)) + + def _set_annotations(self, obj): + if self.local: + self._annotations = obj + else: + raise AttributeError("annotations attribute is read-only") + + annotations = property(_get_annotations, _set_annotations) + + def _get_condition(self): + if self.local: + return self._condition + else: + return cond2obj(pn_disposition_condition(self._impl)) + + def _set_condition(self, obj): + if self.local: + self._condition = obj + else: + raise AttributeError("condition attribute is read-only") + + condition = property(_get_condition, _set_condition) + + +class Delivery(Wrapper): + """ + Tracks and/or records the delivery of a message over a link. + """ + + RECEIVED = Disposition.RECEIVED + ACCEPTED = Disposition.ACCEPTED + REJECTED = Disposition.REJECTED + RELEASED = Disposition.RELEASED + MODIFIED = Disposition.MODIFIED + + @staticmethod + def wrap(impl): + if impl is None: + return None + else: + return Delivery(impl) + + def __init__(self, impl): + Wrapper.__init__(self, impl, pn_delivery_attachments) + + def _init(self): + self.local = Disposition(pn_delivery_local(self._impl), True) + self.remote = Disposition(pn_delivery_remote(self._impl), False) + + @property + def tag(self): + """The identifier for the delivery.""" + return pn_delivery_tag(self._impl) + + @property + def writable(self): + """Returns true for an outgoing delivery to which data can now be written.""" + return pn_delivery_writable(self._impl) + + @property + def readable(self): + """Returns true for an incoming delivery that has data to read.""" + return pn_delivery_readable(self._impl) + + @property + def updated(self): + """Returns true if the state of the delivery has been updated + (e.g. it has been settled and/or accepted, rejected etc).""" + return pn_delivery_updated(self._impl) + + def update(self, state): + """ + Set the local state of the delivery e.g. ACCEPTED, REJECTED, RELEASED. + """ + obj2dat(self.local._data, pn_disposition_data(self.local._impl)) + obj2dat(self.local._annotations, pn_disposition_annotations(self.local._impl)) + obj2cond(self.local._condition, pn_disposition_condition(self.local._impl)) + pn_delivery_update(self._impl, state) + + @property + def pending(self): + return pn_delivery_pending(self._impl) + + @property + def partial(self): + """ + Returns true for an incoming delivery if not all the data is + yet available. + """ + return pn_delivery_partial(self._impl) + + @property + def local_state(self): + """Returns the local state of the delivery.""" + return DispositionType.get(pn_delivery_local_state(self._impl)) + + @property + def remote_state(self): + """ + Returns the state of the delivery as indicated by the remote + peer. + """ + return DispositionType.get(pn_delivery_remote_state(self._impl)) + + @property + def settled(self): + """ + Returns true if the delivery has been settled by the remote peer. + """ + return pn_delivery_settled(self._impl) + + def settle(self): + """ + Settles the delivery locally. This indicates the application + considers the delivery complete and does not wish to receive any + further events about it. Every delivery should be settled locally. + """ + pn_delivery_settle(self._impl) + + @property + def aborted(self): + """Returns true if the delivery has been aborted.""" + return pn_delivery_aborted(self._impl) + + def abort(self): + """ + Aborts the delivery. This indicates the application wishes to + invalidate any data that may have already been sent on this delivery. + The delivery cannot be aborted after it has been completely delivered. + """ + pn_delivery_abort(self._impl) + + @property + def work_next(self): + return Delivery.wrap(pn_work_next(self._impl)) + + @property + def link(self): + """ + Returns the link on which the delivery was sent or received. + """ + from . import _endpoints + return _endpoints.Link.wrap(pn_delivery_link(self._impl)) + + @property + def session(self): + """ + Returns the session over which the delivery was sent or received. + """ + return self.link.session + + @property + def connection(self): + """ + Returns the connection over which the delivery was sent or received. + """ + return self.session.connection + + @property + def transport(self): + return self.connection.transport diff --git a/python/proton/_endpoints.py b/python/proton/_endpoints.py new file mode 100644 index 0000000000..bfa98809d6 --- /dev/null +++ b/python/proton/_endpoints.py @@ -0,0 +1,765 @@ +# +# 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. +# + +""" +The proton.endpoints module +""" + +from __future__ import absolute_import + +import weakref + +from cproton import PN_LOCAL_UNINIT, PN_REMOTE_UNINIT, PN_LOCAL_ACTIVE, PN_REMOTE_ACTIVE, PN_LOCAL_CLOSED, \ + PN_REMOTE_CLOSED, \ + pn_object_reactor, pn_record_get_handler, pn_record_set_handler, pn_decref, \ + pn_connection, pn_connection_attachments, pn_connection_transport, pn_connection_error, pn_connection_condition, \ + pn_connection_remote_condition, pn_connection_collect, pn_connection_set_container, pn_connection_get_container, \ + pn_connection_get_hostname, pn_connection_set_hostname, pn_connection_get_user, pn_connection_set_user, \ + pn_connection_set_password, pn_connection_remote_container, pn_connection_remote_hostname, \ + pn_connection_remote_offered_capabilities, pn_connection_remote_desired_capabilities, \ + pn_connection_remote_properties, pn_connection_offered_capabilities, pn_connection_desired_capabilities, \ + pn_connection_properties, pn_connection_open, pn_connection_close, pn_connection_state, pn_connection_release, \ + pn_session, pn_session_head, pn_session_attachments, pn_session_condition, pn_session_remote_condition, \ + pn_session_get_incoming_capacity, pn_session_set_incoming_capacity, pn_session_get_outgoing_window, \ + pn_session_set_outgoing_window, pn_session_incoming_bytes, pn_session_outgoing_bytes, pn_session_open, \ + pn_session_close, pn_session_next, pn_session_state, pn_session_connection, pn_session_free, \ + PN_SND_UNSETTLED, PN_SND_SETTLED, PN_SND_MIXED, PN_RCV_FIRST, PN_RCV_SECOND, \ + pn_link_head, pn_link_is_sender, pn_link_attachments, pn_link_error, pn_link_condition, pn_link_remote_condition, \ + pn_link_open, pn_link_close, pn_link_state, pn_link_source, pn_link_target, pn_link_remote_source, \ + pn_link_remote_target, pn_link_session, pn_link_current, pn_link_advance, pn_link_unsettled, pn_link_credit, \ + pn_link_available, pn_link_queued, pn_link_next, pn_link_name, pn_link_is_receiver, pn_link_remote_snd_settle_mode, \ + pn_link_remote_rcv_settle_mode, pn_link_snd_settle_mode, pn_link_set_snd_settle_mode, pn_link_rcv_settle_mode, \ + pn_link_set_rcv_settle_mode, pn_link_get_drain, pn_link_set_drain, pn_link_drained, pn_link_remote_max_message_size, \ + pn_link_max_message_size, pn_link_set_max_message_size, pn_link_detach, pn_link_free, pn_link_offered, pn_link_send, \ + pn_link_flow, pn_link_recv, pn_link_drain, pn_link_draining, \ + pn_sender, pn_receiver, \ + PN_UNSPECIFIED, PN_SOURCE, PN_TARGET, PN_COORDINATOR, PN_NONDURABLE, PN_CONFIGURATION, \ + PN_DELIVERIES, PN_DIST_MODE_UNSPECIFIED, PN_DIST_MODE_COPY, PN_DIST_MODE_MOVE, PN_EXPIRE_WITH_LINK, \ + PN_EXPIRE_WITH_SESSION, PN_EXPIRE_WITH_CONNECTION, PN_EXPIRE_NEVER, \ + pn_terminus_set_durability, pn_terminus_set_timeout, pn_terminus_set_dynamic, pn_terminus_get_type, \ + pn_terminus_get_durability, pn_terminus_set_type, pn_terminus_get_address, pn_terminus_capabilities, \ + pn_terminus_set_address, pn_terminus_get_timeout, pn_terminus_filter, pn_terminus_properties, \ + pn_terminus_get_expiry_policy, pn_terminus_set_expiry_policy, pn_terminus_set_distribution_mode, \ + pn_terminus_get_distribution_mode, pn_terminus_copy, pn_terminus_outcomes, pn_terminus_is_dynamic, \ + PN_EOS, \ + pn_delivery, \ + pn_work_head, \ + pn_error_code, pn_error_text + +from ._common import utf82unicode, unicode2utf8 +from ._condition import obj2cond, cond2obj +from ._data import Data, obj2dat, dat2obj +from ._delivery import Delivery +from ._exceptions import EXCEPTIONS, LinkException, SessionException, ConnectionException +from ._transport import Transport +from ._wrapper import Wrapper + + +class Endpoint(object): + LOCAL_UNINIT = PN_LOCAL_UNINIT + REMOTE_UNINIT = PN_REMOTE_UNINIT + LOCAL_ACTIVE = PN_LOCAL_ACTIVE + REMOTE_ACTIVE = PN_REMOTE_ACTIVE + LOCAL_CLOSED = PN_LOCAL_CLOSED + REMOTE_CLOSED = PN_REMOTE_CLOSED + + def _init(self): + self.condition = None + + def _update_cond(self): + obj2cond(self.condition, self._get_cond_impl()) + + @property + def remote_condition(self): + return cond2obj(self._get_remote_cond_impl()) + + # the following must be provided by subclasses + def _get_cond_impl(self): + assert False, "Subclass must override this!" + + def _get_remote_cond_impl(self): + assert False, "Subclass must override this!" + + def _get_handler(self): + from . import reactor + from . import _reactor_impl + ractor = reactor.Reactor.wrap(pn_object_reactor(self._impl)) + if ractor: + on_error = ractor.on_error_delegate() + else: + on_error = None + record = self._get_attachments() + return _reactor_impl.WrappedHandler.wrap(pn_record_get_handler(record), on_error) + + def _set_handler(self, handler): + from . import reactor + from . import _reactor_impl + ractor = reactor.Reactor.wrap(pn_object_reactor(self._impl)) + if ractor: + on_error = ractor.on_error_delegate() + else: + on_error = None + impl = _reactor_impl._chandler(handler, on_error) + record = self._get_attachments() + pn_record_set_handler(record, impl) + pn_decref(impl) + + handler = property(_get_handler, _set_handler) + + @property + def transport(self): + return self.connection.transport + + +class Connection(Wrapper, Endpoint): + """ + A representation of an AMQP connection + """ + + @staticmethod + def wrap(impl): + if impl is None: + return None + else: + return Connection(impl) + + def __init__(self, impl=pn_connection): + Wrapper.__init__(self, impl, pn_connection_attachments) + + def _init(self): + Endpoint._init(self) + self.offered_capabilities = None + self.desired_capabilities = None + self.properties = None + + def _get_attachments(self): + return pn_connection_attachments(self._impl) + + @property + def connection(self): + return self + + @property + def transport(self): + return Transport.wrap(pn_connection_transport(self._impl)) + + def _check(self, err): + if err < 0: + exc = EXCEPTIONS.get(err, ConnectionException) + raise exc("[%s]: %s" % (err, pn_connection_error(self._impl))) + else: + return err + + def _get_cond_impl(self): + return pn_connection_condition(self._impl) + + def _get_remote_cond_impl(self): + return pn_connection_remote_condition(self._impl) + + def collect(self, collector): + if collector is None: + pn_connection_collect(self._impl, None) + else: + pn_connection_collect(self._impl, collector._impl) + self._collector = weakref.ref(collector) + + def _get_container(self): + return utf82unicode(pn_connection_get_container(self._impl)) + + def _set_container(self, name): + return pn_connection_set_container(self._impl, unicode2utf8(name)) + + container = property(_get_container, _set_container) + + def _get_hostname(self): + return utf82unicode(pn_connection_get_hostname(self._impl)) + + def _set_hostname(self, name): + return pn_connection_set_hostname(self._impl, unicode2utf8(name)) + + hostname = property(_get_hostname, _set_hostname, + doc=""" +Set the name of the host (either fully qualified or relative) to which this +connection is connecting to. This information may be used by the remote +peer to determine the correct back-end service to connect the client to. +This value will be sent in the Open performative, and will be used by SSL +and SASL layers to identify the peer. +""") + + def _get_user(self): + return utf82unicode(pn_connection_get_user(self._impl)) + + def _set_user(self, name): + return pn_connection_set_user(self._impl, unicode2utf8(name)) + + user = property(_get_user, _set_user) + + def _get_password(self): + return None + + def _set_password(self, name): + return pn_connection_set_password(self._impl, unicode2utf8(name)) + + password = property(_get_password, _set_password) + + @property + def remote_container(self): + """The container identifier specified by the remote peer for this connection.""" + return pn_connection_remote_container(self._impl) + + @property + def remote_hostname(self): + """The hostname specified by the remote peer for this connection.""" + return pn_connection_remote_hostname(self._impl) + + @property + def remote_offered_capabilities(self): + """The capabilities offered by the remote peer for this connection.""" + return dat2obj(pn_connection_remote_offered_capabilities(self._impl)) + + @property + def remote_desired_capabilities(self): + """The capabilities desired by the remote peer for this connection.""" + return dat2obj(pn_connection_remote_desired_capabilities(self._impl)) + + @property + def remote_properties(self): + """The properties specified by the remote peer for this connection.""" + return dat2obj(pn_connection_remote_properties(self._impl)) + + def open(self): + """ + Opens the connection. + + In more detail, this moves the local state of the connection to + the ACTIVE state and triggers an open frame to be sent to the + peer. A connection is fully active once both peers have opened it. + """ + obj2dat(self.offered_capabilities, + pn_connection_offered_capabilities(self._impl)) + obj2dat(self.desired_capabilities, + pn_connection_desired_capabilities(self._impl)) + obj2dat(self.properties, pn_connection_properties(self._impl)) + pn_connection_open(self._impl) + + def close(self): + """ + Closes the connection. + + In more detail, this moves the local state of the connection to + the CLOSED state and triggers a close frame to be sent to the + peer. A connection is fully closed once both peers have closed it. + """ + self._update_cond() + pn_connection_close(self._impl) + if hasattr(self, '_session_policy'): + # break circular ref + del self._session_policy + + @property + def state(self): + """ + The state of the connection as a bit field. The state has a local + and a remote component. Each of these can be in one of three + states: UNINIT, ACTIVE or CLOSED. These can be tested by masking + against LOCAL_UNINIT, LOCAL_ACTIVE, LOCAL_CLOSED, REMOTE_UNINIT, + REMOTE_ACTIVE and REMOTE_CLOSED. + """ + return pn_connection_state(self._impl) + + def session(self): + """ + Returns a new session on this connection. + """ + ssn = pn_session(self._impl) + if ssn is None: + raise (SessionException("Session allocation failed.")) + else: + return Session(ssn) + + def session_head(self, mask): + return Session.wrap(pn_session_head(self._impl, mask)) + + def link_head(self, mask): + return Link.wrap(pn_link_head(self._impl, mask)) + + @property + def work_head(self): + return Delivery.wrap(pn_work_head(self._impl)) + + @property + def error(self): + return pn_error_code(pn_connection_error(self._impl)) + + def free(self): + pn_connection_release(self._impl) + + +class Session(Wrapper, Endpoint): + + @staticmethod + def wrap(impl): + if impl is None: + return None + else: + return Session(impl) + + def __init__(self, impl): + Wrapper.__init__(self, impl, pn_session_attachments) + + def _get_attachments(self): + return pn_session_attachments(self._impl) + + def _get_cond_impl(self): + return pn_session_condition(self._impl) + + def _get_remote_cond_impl(self): + return pn_session_remote_condition(self._impl) + + def _get_incoming_capacity(self): + return pn_session_get_incoming_capacity(self._impl) + + def _set_incoming_capacity(self, capacity): + pn_session_set_incoming_capacity(self._impl, capacity) + + incoming_capacity = property(_get_incoming_capacity, _set_incoming_capacity) + + def _get_outgoing_window(self): + return pn_session_get_outgoing_window(self._impl) + + def _set_outgoing_window(self, window): + pn_session_set_outgoing_window(self._impl, window) + + outgoing_window = property(_get_outgoing_window, _set_outgoing_window) + + @property + def outgoing_bytes(self): + return pn_session_outgoing_bytes(self._impl) + + @property + def incoming_bytes(self): + return pn_session_incoming_bytes(self._impl) + + def open(self): + pn_session_open(self._impl) + + def close(self): + self._update_cond() + pn_session_close(self._impl) + + def next(self, mask): + return Session.wrap(pn_session_next(self._impl, mask)) + + @property + def state(self): + return pn_session_state(self._impl) + + @property + def connection(self): + return Connection.wrap(pn_session_connection(self._impl)) + + def sender(self, name): + return Sender(pn_sender(self._impl, unicode2utf8(name))) + + def receiver(self, name): + return Receiver(pn_receiver(self._impl, unicode2utf8(name))) + + def free(self): + pn_session_free(self._impl) + + +class Link(Wrapper, Endpoint): + """ + A representation of an AMQP link, of which there are two concrete + implementations, Sender and Receiver. + """ + + SND_UNSETTLED = PN_SND_UNSETTLED + SND_SETTLED = PN_SND_SETTLED + SND_MIXED = PN_SND_MIXED + + RCV_FIRST = PN_RCV_FIRST + RCV_SECOND = PN_RCV_SECOND + + @staticmethod + def wrap(impl): + if impl is None: return None + if pn_link_is_sender(impl): + return Sender(impl) + else: + return Receiver(impl) + + def __init__(self, impl): + Wrapper.__init__(self, impl, pn_link_attachments) + + def _get_attachments(self): + return pn_link_attachments(self._impl) + + def _check(self, err): + if err < 0: + exc = EXCEPTIONS.get(err, LinkException) + raise exc("[%s]: %s" % (err, pn_error_text(pn_link_error(self._impl)))) + else: + return err + + def _get_cond_impl(self): + return pn_link_condition(self._impl) + + def _get_remote_cond_impl(self): + return pn_link_remote_condition(self._impl) + + def open(self): + """ + Opens the link. + + In more detail, this moves the local state of the link to the + ACTIVE state and triggers an attach frame to be sent to the + peer. A link is fully active once both peers have attached it. + """ + pn_link_open(self._impl) + + def close(self): + """ + Closes the link. + + In more detail, this moves the local state of the link to the + CLOSED state and triggers an detach frame (with the closed flag + set) to be sent to the peer. A link is fully closed once both + peers have detached it. + """ + self._update_cond() + pn_link_close(self._impl) + + @property + def state(self): + """ + The state of the link as a bit field. The state has a local + and a remote component. Each of these can be in one of three + states: UNINIT, ACTIVE or CLOSED. These can be tested by masking + against LOCAL_UNINIT, LOCAL_ACTIVE, LOCAL_CLOSED, REMOTE_UNINIT, + REMOTE_ACTIVE and REMOTE_CLOSED. + """ + return pn_link_state(self._impl) + + @property + def source(self): + """The source of the link as described by the local peer.""" + return Terminus(pn_link_source(self._impl)) + + @property + def target(self): + """The target of the link as described by the local peer.""" + return Terminus(pn_link_target(self._impl)) + + @property + def remote_source(self): + """The source of the link as described by the remote peer.""" + return Terminus(pn_link_remote_source(self._impl)) + + @property + def remote_target(self): + """The target of the link as described by the remote peer.""" + return Terminus(pn_link_remote_target(self._impl)) + + @property + def session(self): + return Session.wrap(pn_link_session(self._impl)) + + @property + def connection(self): + """The connection on which this link was attached.""" + return self.session.connection + + def delivery(self, tag): + return Delivery(pn_delivery(self._impl, tag)) + + @property + def current(self): + return Delivery.wrap(pn_link_current(self._impl)) + + def advance(self): + return pn_link_advance(self._impl) + + @property + def unsettled(self): + return pn_link_unsettled(self._impl) + + @property + def credit(self): + """The amount of outstanding credit on this link.""" + return pn_link_credit(self._impl) + + @property + def available(self): + return pn_link_available(self._impl) + + @property + def queued(self): + return pn_link_queued(self._impl) + + def next(self, mask): + return Link.wrap(pn_link_next(self._impl, mask)) + + @property + def name(self): + """Returns the name of the link""" + return utf82unicode(pn_link_name(self._impl)) + + @property + def is_sender(self): + """Returns true if this link is a sender.""" + return pn_link_is_sender(self._impl) + + @property + def is_receiver(self): + """Returns true if this link is a receiver.""" + return pn_link_is_receiver(self._impl) + + @property + def remote_snd_settle_mode(self): + return pn_link_remote_snd_settle_mode(self._impl) + + @property + def remote_rcv_settle_mode(self): + return pn_link_remote_rcv_settle_mode(self._impl) + + def _get_snd_settle_mode(self): + return pn_link_snd_settle_mode(self._impl) + + def _set_snd_settle_mode(self, mode): + pn_link_set_snd_settle_mode(self._impl, mode) + + snd_settle_mode = property(_get_snd_settle_mode, _set_snd_settle_mode) + + def _get_rcv_settle_mode(self): + return pn_link_rcv_settle_mode(self._impl) + + def _set_rcv_settle_mode(self, mode): + pn_link_set_rcv_settle_mode(self._impl, mode) + + rcv_settle_mode = property(_get_rcv_settle_mode, _set_rcv_settle_mode) + + def _get_drain(self): + return pn_link_get_drain(self._impl) + + def _set_drain(self, b): + pn_link_set_drain(self._impl, bool(b)) + + drain_mode = property(_get_drain, _set_drain) + + def drained(self): + return pn_link_drained(self._impl) + + @property + def remote_max_message_size(self): + return pn_link_remote_max_message_size(self._impl) + + def _get_max_message_size(self): + return pn_link_max_message_size(self._impl) + + def _set_max_message_size(self, mode): + pn_link_set_max_message_size(self._impl, mode) + + max_message_size = property(_get_max_message_size, _set_max_message_size) + + def detach(self): + return pn_link_detach(self._impl) + + def free(self): + pn_link_free(self._impl) + + +class Sender(Link): + """ + A link over which messages are sent. + """ + + def offered(self, n): + pn_link_offered(self._impl, n) + + def stream(self, data): + """ + Send specified data as part of the current delivery + + @type data: binary + @param data: data to send + """ + return self._check(pn_link_send(self._impl, data)) + + def send(self, obj, tag=None): + """ + Send specified object over this sender; the object is expected to + have a send() method on it that takes the sender and an optional + tag as arguments. + + Where the object is a Message, this will send the message over + this link, creating a new delivery for the purpose. + """ + if hasattr(obj, 'send'): + return obj.send(self, tag=tag) + else: + # treat object as bytes + return self.stream(obj) + + def delivery_tag(self): + if not hasattr(self, 'tag_generator'): + def simple_tags(): + count = 1 + while True: + yield str(count) + count += 1 + + self.tag_generator = simple_tags() + return next(self.tag_generator) + + +class Receiver(Link): + """ + A link over which messages are received. + """ + + def flow(self, n): + """Increases the credit issued to the remote sender by the specified number of messages.""" + pn_link_flow(self._impl, n) + + def recv(self, limit): + n, binary = pn_link_recv(self._impl, limit) + if n == PN_EOS: + return None + else: + self._check(n) + return binary + + def drain(self, n): + pn_link_drain(self._impl, n) + + def draining(self): + return pn_link_draining(self._impl) + + +class Terminus(object): + UNSPECIFIED = PN_UNSPECIFIED + SOURCE = PN_SOURCE + TARGET = PN_TARGET + COORDINATOR = PN_COORDINATOR + + NONDURABLE = PN_NONDURABLE + CONFIGURATION = PN_CONFIGURATION + DELIVERIES = PN_DELIVERIES + + DIST_MODE_UNSPECIFIED = PN_DIST_MODE_UNSPECIFIED + DIST_MODE_COPY = PN_DIST_MODE_COPY + DIST_MODE_MOVE = PN_DIST_MODE_MOVE + + EXPIRE_WITH_LINK = PN_EXPIRE_WITH_LINK + EXPIRE_WITH_SESSION = PN_EXPIRE_WITH_SESSION + EXPIRE_WITH_CONNECTION = PN_EXPIRE_WITH_CONNECTION + EXPIRE_NEVER = PN_EXPIRE_NEVER + + def __init__(self, impl): + self._impl = impl + + def _check(self, err): + if err < 0: + exc = EXCEPTIONS.get(err, LinkException) + raise exc("[%s]" % err) + else: + return err + + def _get_type(self): + return pn_terminus_get_type(self._impl) + + def _set_type(self, type): + self._check(pn_terminus_set_type(self._impl, type)) + + type = property(_get_type, _set_type) + + def _get_address(self): + """The address that identifies the source or target node""" + return utf82unicode(pn_terminus_get_address(self._impl)) + + def _set_address(self, address): + self._check(pn_terminus_set_address(self._impl, unicode2utf8(address))) + + address = property(_get_address, _set_address) + + def _get_durability(self): + return pn_terminus_get_durability(self._impl) + + def _set_durability(self, seconds): + self._check(pn_terminus_set_durability(self._impl, seconds)) + + durability = property(_get_durability, _set_durability) + + def _get_expiry_policy(self): + return pn_terminus_get_expiry_policy(self._impl) + + def _set_expiry_policy(self, seconds): + self._check(pn_terminus_set_expiry_policy(self._impl, seconds)) + + expiry_policy = property(_get_expiry_policy, _set_expiry_policy) + + def _get_timeout(self): + return pn_terminus_get_timeout(self._impl) + + def _set_timeout(self, seconds): + self._check(pn_terminus_set_timeout(self._impl, seconds)) + + timeout = property(_get_timeout, _set_timeout) + + def _is_dynamic(self): + """Indicates whether the source or target node was dynamically + created""" + return pn_terminus_is_dynamic(self._impl) + + def _set_dynamic(self, dynamic): + self._check(pn_terminus_set_dynamic(self._impl, dynamic)) + + dynamic = property(_is_dynamic, _set_dynamic) + + def _get_distribution_mode(self): + return pn_terminus_get_distribution_mode(self._impl) + + def _set_distribution_mode(self, mode): + self._check(pn_terminus_set_distribution_mode(self._impl, mode)) + + distribution_mode = property(_get_distribution_mode, _set_distribution_mode) + + @property + def properties(self): + """Properties of a dynamic source or target.""" + return Data(pn_terminus_properties(self._impl)) + + @property + def capabilities(self): + """Capabilities of the source or target.""" + return Data(pn_terminus_capabilities(self._impl)) + + @property + def outcomes(self): + return Data(pn_terminus_outcomes(self._impl)) + + @property + def filter(self): + """A filter on a source allows the set of messages transfered over + the link to be restricted""" + return Data(pn_terminus_filter(self._impl)) + + def copy(self, src): + self._check(pn_terminus_copy(self._impl, src._impl)) diff --git a/python/proton/_events.py b/python/proton/_events.py new file mode 100644 index 0000000000..c8af8e2c07 --- /dev/null +++ b/python/proton/_events.py @@ -0,0 +1,333 @@ +# +# 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 threading + +from cproton import PN_SESSION_REMOTE_CLOSE, PN_SESSION_FINAL, pn_event_context, pn_collector_put, \ + PN_SELECTABLE_UPDATED, pn_collector, PN_CONNECTION_REMOTE_OPEN, pn_event_attachments, pn_event_type, \ + pn_collector_free, pn_handler_dispatch, PN_SELECTABLE_WRITABLE, PN_SELECTABLE_INIT, PN_SESSION_REMOTE_OPEN, \ + pn_collector_peek, PN_CONNECTION_BOUND, PN_LINK_FLOW, pn_event_connection, PN_LINK_LOCAL_CLOSE, \ + PN_TRANSPORT_ERROR, PN_CONNECTION_LOCAL_OPEN, PN_CONNECTION_LOCAL_CLOSE, pn_event_delivery, \ + PN_LINK_REMOTE_OPEN, PN_TRANSPORT_CLOSED, PN_TRANSPORT_HEAD_CLOSED, PN_TRANSPORT, pn_event_reactor, \ + PN_CONNECTION_REMOTE_CLOSE, pn_collector_pop, PN_LINK_INIT, pn_event_link, PN_CONNECTION_UNBOUND, \ + pn_event_type_name, pn_event_session, PN_LINK_FINAL, pn_py2void, PN_REACTOR_INIT, PN_REACTOR_QUIESCED, \ + PN_LINK_LOCAL_DETACH, PN_SESSION_INIT, PN_CONNECTION_FINAL, PN_TIMER_TASK, pn_class_name, PN_SELECTABLE_READABLE, \ + pn_event_transport, PN_TRANSPORT_TAIL_CLOSED, PN_SELECTABLE_FINAL, PN_SESSION_LOCAL_OPEN, PN_DELIVERY, \ + PN_SESSION_LOCAL_CLOSE, pn_event_copy, PN_REACTOR_FINAL, PN_LINK_LOCAL_OPEN, PN_SELECTABLE_EXPIRED, \ + PN_LINK_REMOTE_DETACH, PN_PYREF, PN_LINK_REMOTE_CLOSE, pn_event_root, PN_SELECTABLE_ERROR, \ + PN_CONNECTION_INIT, pn_event_class, pn_void2py, pn_cast_pn_session, pn_cast_pn_link, pn_cast_pn_delivery, \ + pn_cast_pn_transport, pn_cast_pn_connection, pn_cast_pn_selectable + +from ._common import Constant +from ._delivery import Delivery +from ._endpoints import Connection, Session, Link +from ._reactor_impl import Selectable, WrappedHandler +from ._transport import Transport +from ._wrapper import Wrapper + + +class Collector: + + def __init__(self): + self._impl = pn_collector() + + def put(self, obj, etype): + pn_collector_put(self._impl, PN_PYREF, pn_py2void(obj), etype.number) + + def peek(self): + return Event.wrap(pn_collector_peek(self._impl)) + + def pop(self): + ev = self.peek() + pn_collector_pop(self._impl) + + def __del__(self): + pn_collector_free(self._impl) + del self._impl + + +if "TypeExtender" not in globals(): + class TypeExtender: + def __init__(self, number): + self.number = number + + def next(self): + try: + return self.number + finally: + self.number += 1 + + +class EventType(object): + _lock = threading.Lock() + _extended = TypeExtender(10000) + TYPES = {} + + def __init__(self, name=None, number=None, method=None): + if name is None and number is None: + raise TypeError("extended events require a name") + try: + self._lock.acquire() + if name is None: + name = pn_event_type_name(number) + + if number is None: + number = self._extended.next() + + if method is None: + method = "on_%s" % name + + self.name = name + self.number = number + self.method = method + + self.TYPES[number] = self + finally: + self._lock.release() + + def __repr__(self): + return self.name + + +def dispatch(handler, method, *args): + m = getattr(handler, method, None) + if m: + return m(*args) + elif hasattr(handler, "on_unhandled"): + return handler.on_unhandled(method, *args) + + +class EventBase(object): + + def __init__(self, clazz, context, type): + self.clazz = clazz + self.context = context + self.type = type + + def dispatch(self, handler): + return dispatch(handler, self.type.method, self) + + +def _none(x): return None + + +DELEGATED = Constant("DELEGATED") + + +def _core(number, method): + return EventType(number=number, method=method) + + +wrappers = { + "pn_void": lambda x: pn_void2py(x), + "pn_pyref": lambda x: pn_void2py(x), + "pn_connection": lambda x: Connection.wrap(pn_cast_pn_connection(x)), + "pn_session": lambda x: Session.wrap(pn_cast_pn_session(x)), + "pn_link": lambda x: Link.wrap(pn_cast_pn_link(x)), + "pn_delivery": lambda x: Delivery.wrap(pn_cast_pn_delivery(x)), + "pn_transport": lambda x: Transport.wrap(pn_cast_pn_transport(x)), + "pn_selectable": lambda x: Selectable.wrap(pn_cast_pn_selectable(x)) +} + + +class Event(Wrapper, EventBase): + REACTOR_INIT = _core(PN_REACTOR_INIT, "on_reactor_init") + REACTOR_QUIESCED = _core(PN_REACTOR_QUIESCED, "on_reactor_quiesced") + REACTOR_FINAL = _core(PN_REACTOR_FINAL, "on_reactor_final") + + TIMER_TASK = _core(PN_TIMER_TASK, "on_timer_task") + + CONNECTION_INIT = _core(PN_CONNECTION_INIT, "on_connection_init") + CONNECTION_BOUND = _core(PN_CONNECTION_BOUND, "on_connection_bound") + CONNECTION_UNBOUND = _core(PN_CONNECTION_UNBOUND, "on_connection_unbound") + CONNECTION_LOCAL_OPEN = _core(PN_CONNECTION_LOCAL_OPEN, "on_connection_local_open") + CONNECTION_LOCAL_CLOSE = _core(PN_CONNECTION_LOCAL_CLOSE, "on_connection_local_close") + CONNECTION_REMOTE_OPEN = _core(PN_CONNECTION_REMOTE_OPEN, "on_connection_remote_open") + CONNECTION_REMOTE_CLOSE = _core(PN_CONNECTION_REMOTE_CLOSE, "on_connection_remote_close") + CONNECTION_FINAL = _core(PN_CONNECTION_FINAL, "on_connection_final") + + SESSION_INIT = _core(PN_SESSION_INIT, "on_session_init") + SESSION_LOCAL_OPEN = _core(PN_SESSION_LOCAL_OPEN, "on_session_local_open") + SESSION_LOCAL_CLOSE = _core(PN_SESSION_LOCAL_CLOSE, "on_session_local_close") + SESSION_REMOTE_OPEN = _core(PN_SESSION_REMOTE_OPEN, "on_session_remote_open") + SESSION_REMOTE_CLOSE = _core(PN_SESSION_REMOTE_CLOSE, "on_session_remote_close") + SESSION_FINAL = _core(PN_SESSION_FINAL, "on_session_final") + + LINK_INIT = _core(PN_LINK_INIT, "on_link_init") + LINK_LOCAL_OPEN = _core(PN_LINK_LOCAL_OPEN, "on_link_local_open") + LINK_LOCAL_CLOSE = _core(PN_LINK_LOCAL_CLOSE, "on_link_local_close") + LINK_LOCAL_DETACH = _core(PN_LINK_LOCAL_DETACH, "on_link_local_detach") + LINK_REMOTE_OPEN = _core(PN_LINK_REMOTE_OPEN, "on_link_remote_open") + LINK_REMOTE_CLOSE = _core(PN_LINK_REMOTE_CLOSE, "on_link_remote_close") + LINK_REMOTE_DETACH = _core(PN_LINK_REMOTE_DETACH, "on_link_remote_detach") + LINK_FLOW = _core(PN_LINK_FLOW, "on_link_flow") + LINK_FINAL = _core(PN_LINK_FINAL, "on_link_final") + + DELIVERY = _core(PN_DELIVERY, "on_delivery") + + TRANSPORT = _core(PN_TRANSPORT, "on_transport") + TRANSPORT_ERROR = _core(PN_TRANSPORT_ERROR, "on_transport_error") + TRANSPORT_HEAD_CLOSED = _core(PN_TRANSPORT_HEAD_CLOSED, "on_transport_head_closed") + TRANSPORT_TAIL_CLOSED = _core(PN_TRANSPORT_TAIL_CLOSED, "on_transport_tail_closed") + TRANSPORT_CLOSED = _core(PN_TRANSPORT_CLOSED, "on_transport_closed") + + SELECTABLE_INIT = _core(PN_SELECTABLE_INIT, "on_selectable_init") + SELECTABLE_UPDATED = _core(PN_SELECTABLE_UPDATED, "on_selectable_updated") + SELECTABLE_READABLE = _core(PN_SELECTABLE_READABLE, "on_selectable_readable") + SELECTABLE_WRITABLE = _core(PN_SELECTABLE_WRITABLE, "on_selectable_writable") + SELECTABLE_EXPIRED = _core(PN_SELECTABLE_EXPIRED, "on_selectable_expired") + SELECTABLE_ERROR = _core(PN_SELECTABLE_ERROR, "on_selectable_error") + SELECTABLE_FINAL = _core(PN_SELECTABLE_FINAL, "on_selectable_final") + + @staticmethod + def wrap(impl, number=None): + if impl is None: + return None + + if number is None: + number = pn_event_type(impl) + + event = Event(impl, number) + + # check for an application defined ApplicationEvent and return that. This + # avoids an expensive wrap operation invoked by event.context + if pn_event_class(impl) == PN_PYREF and \ + isinstance(event.context, EventBase): + return event.context + else: + return event + + def __init__(self, impl, number): + Wrapper.__init__(self, impl, pn_event_attachments) + self.__dict__["type"] = EventType.TYPES[number] + + def _init(self): + pass + + def copy(self): + copy = pn_event_copy(self._impl) + return Event.wrap(copy) + + @property + def clazz(self): + cls = pn_event_class(self._impl) + if cls: + return pn_class_name(cls) + else: + return None + + @property + def root(self): + return WrappedHandler.wrap(pn_event_root(self._impl)) + + @property + def context(self): + """Returns the context object associated with the event. The type of this depend on the type of event.""" + return wrappers[self.clazz](pn_event_context(self._impl)) + + def dispatch(self, handler, type=None): + type = type or self.type + if isinstance(handler, WrappedHandler): + pn_handler_dispatch(handler._impl, self._impl, type.number) + else: + result = dispatch(handler, type.method, self) + if result != DELEGATED and hasattr(handler, "handlers"): + for h in handler.handlers: + self.dispatch(h, type) + + @property + def reactor(self): + """Returns the reactor associated with the event.""" + return wrappers.get("pn_reactor", _none)(pn_event_reactor(self._impl)) + + def __getattr__(self, name): + r = self.reactor + if r and hasattr(r, 'subclass') and r.subclass.__name__.lower() == name: + return r + else: + return super(Event, self).__getattr__(name) + + @property + def transport(self): + """Returns the transport associated with the event, or null if none is associated with it.""" + return Transport.wrap(pn_event_transport(self._impl)) + + @property + def connection(self): + """Returns the connection associated with the event, or null if none is associated with it.""" + return Connection.wrap(pn_event_connection(self._impl)) + + @property + def session(self): + """Returns the session associated with the event, or null if none is associated with it.""" + return Session.wrap(pn_event_session(self._impl)) + + @property + def link(self): + """Returns the link associated with the event, or null if none is associated with it.""" + return Link.wrap(pn_event_link(self._impl)) + + @property + def sender(self): + """Returns the sender link associated with the event, or null if + none is associated with it. This is essentially an alias for + link(), that does an additional checkon the type of the + link.""" + l = self.link + if l and l.is_sender: + return l + else: + return None + + @property + def receiver(self): + """Returns the receiver link associated with the event, or null if + none is associated with it. This is essentially an alias for + link(), that does an additional checkon the type of the link.""" + l = self.link + if l and l.is_receiver: + return l + else: + return None + + @property + def delivery(self): + """Returns the delivery associated with the event, or null if none is associated with it.""" + return Delivery.wrap(pn_event_delivery(self._impl)) + + def __repr__(self): + return "%s(%s)" % (self.type, self.context) + + +class LazyHandlers(object): + def __get__(self, obj, clazz): + if obj is None: + return self + ret = [] + obj.__dict__['handlers'] = ret + return ret + + +class Handler(object): + handlers = LazyHandlers() + + def on_unhandled(self, method, *args): + pass diff --git a/python/proton/_exceptions.py b/python/proton/_exceptions.py new file mode 100644 index 0000000000..47420c2fcd --- /dev/null +++ b/python/proton/_exceptions.py @@ -0,0 +1,92 @@ +# +# 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 + +from cproton import PN_TIMEOUT, PN_INTR + + +class ProtonException(Exception): + """ + The root of the proton exception hierarchy. All proton exception + classes derive from this exception. + """ + pass + + +class Timeout(ProtonException): + """ + A timeout exception indicates that a blocking operation has timed + out. + """ + pass + + +class Interrupt(ProtonException): + """ + An interrupt exception indicates that a blocking operation was interrupted. + """ + pass + + +EXCEPTIONS = { + PN_TIMEOUT: Timeout, + PN_INTR: Interrupt +} + + +class MessageException(ProtonException): + """ + The MessageException class is the root of the message exception + hierarchy. All exceptions generated by the Message class derive from + this exception. + """ + pass + + +class DataException(ProtonException): + """ + The DataException class is the root of the Data exception hierarchy. + All exceptions raised by the Data class extend this exception. + """ + pass + + +class TransportException(ProtonException): + pass + + +class SSLException(TransportException): + pass + + +class SSLUnavailable(SSLException): + pass + + +class ConnectionException(ProtonException): + pass + + +class SessionException(ProtonException): + pass + + +class LinkException(ProtonException): + pass diff --git a/python/proton/_message.py b/python/proton/_message.py new file mode 100644 index 0000000000..32a8c72f57 --- /dev/null +++ b/python/proton/_message.py @@ -0,0 +1,465 @@ +# +# 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 + +from cproton import PN_STATUS_SETTLED, PN_DEFAULT_PRIORITY, PN_STATUS_MODIFIED, PN_STATUS_RELEASED, PN_STATUS_ABORTED, \ + PN_STATUS_REJECTED, PN_STATUS_PENDING, PN_STATUS_UNKNOWN, PN_STATUS_ACCEPTED, \ + PN_OVERFLOW, \ + pn_message_set_delivery_count, pn_message_set_address, pn_message_properties, \ + pn_message_get_user_id, pn_message_set_content_encoding, pn_message_get_subject, pn_message_get_priority, \ + pn_message_get_content_encoding, pn_message_body, \ + pn_message_correlation_id, pn_message_get_address, pn_message_set_content_type, pn_message_get_group_id, \ + pn_message_set_expiry_time, pn_message_set_creation_time, pn_message_error, \ + pn_message_is_first_acquirer, pn_message_set_priority, \ + pn_message_free, pn_message_get_creation_time, pn_message_is_inferred, pn_message_set_subject, \ + pn_message_set_user_id, pn_message_set_group_id, \ + pn_message_id, pn_message_clear, pn_message_set_durable, \ + pn_message_set_first_acquirer, pn_message_get_delivery_count, \ + pn_message_decode, pn_message_set_reply_to_group_id, \ + pn_message_get_group_sequence, pn_message_set_reply_to, \ + pn_message_set_ttl, pn_message_get_reply_to, pn_message, pn_message_annotations, pn_message_is_durable, \ + pn_message_instructions, pn_message_get_content_type, \ + pn_message_get_reply_to_group_id, pn_message_get_ttl, pn_message_encode, pn_message_get_expiry_time, \ + pn_message_set_group_sequence, pn_message_set_inferred, \ + pn_inspect, pn_string, pn_string_get, pn_free, pn_error_text + +from . import _compat +from ._common import Constant, isinteger, secs2millis, millis2secs, unicode2utf8, utf82unicode +from ._data import Data, ulong, symbol +from ._endpoints import Link +from ._exceptions import EXCEPTIONS, MessageException + +PENDING = Constant("PENDING") +ACCEPTED = Constant("ACCEPTED") +REJECTED = Constant("REJECTED") +RELEASED = Constant("RELEASED") +MODIFIED = Constant("MODIFIED") +ABORTED = Constant("ABORTED") +SETTLED = Constant("SETTLED") + +STATUSES = { + PN_STATUS_ABORTED: ABORTED, + PN_STATUS_ACCEPTED: ACCEPTED, + PN_STATUS_REJECTED: REJECTED, + PN_STATUS_RELEASED: RELEASED, + PN_STATUS_MODIFIED: MODIFIED, + PN_STATUS_PENDING: PENDING, + PN_STATUS_SETTLED: SETTLED, + PN_STATUS_UNKNOWN: None +} + + +class Message(object): + """The L{Message} class is a mutable holder of message content. + + @ivar instructions: delivery instructions for the message + @type instructions: dict + @ivar annotations: infrastructure defined message annotations + @type annotations: dict + @ivar properties: application defined message properties + @type properties: dict + @ivar body: message body + @type body: bytes | unicode | dict | list | int | long | float | UUID + """ + + DEFAULT_PRIORITY = PN_DEFAULT_PRIORITY + + def __init__(self, body=None, **kwargs): + """ + @param kwargs: Message property name/value pairs to initialise the Message + """ + self._msg = pn_message() + self._id = Data(pn_message_id(self._msg)) + self._correlation_id = Data(pn_message_correlation_id(self._msg)) + self.instructions = None + self.annotations = None + self.properties = None + self.body = body + for k, v in _compat.iteritems(kwargs): + getattr(self, k) # Raise exception if it's not a valid attribute. + setattr(self, k, v) + + def __del__(self): + if hasattr(self, "_msg"): + pn_message_free(self._msg) + del self._msg + + def _check(self, err): + if err < 0: + exc = EXCEPTIONS.get(err, MessageException) + raise exc("[%s]: %s" % (err, pn_error_text(pn_message_error(self._msg)))) + else: + return err + + def _check_property_keys(self): + for k in self.properties.keys(): + if isinstance(k, unicode): + # py2 unicode, py3 str (via hack definition) + continue + # If key is binary then change to string + elif isinstance(k, str): + # py2 str + self.properties[k.encode('utf-8')] = self.properties.pop(k) + else: + raise MessageException('Application property key is not string type: key=%s %s' % (str(k), type(k))) + + def _pre_encode(self): + inst = Data(pn_message_instructions(self._msg)) + ann = Data(pn_message_annotations(self._msg)) + props = Data(pn_message_properties(self._msg)) + body = Data(pn_message_body(self._msg)) + + inst.clear() + if self.instructions is not None: + inst.put_object(self.instructions) + ann.clear() + if self.annotations is not None: + ann.put_object(self.annotations) + props.clear() + if self.properties is not None: + self._check_property_keys() + props.put_object(self.properties) + body.clear() + if self.body is not None: + body.put_object(self.body) + + def _post_decode(self): + inst = Data(pn_message_instructions(self._msg)) + ann = Data(pn_message_annotations(self._msg)) + props = Data(pn_message_properties(self._msg)) + body = Data(pn_message_body(self._msg)) + + if inst.next(): + self.instructions = inst.get_object() + else: + self.instructions = None + if ann.next(): + self.annotations = ann.get_object() + else: + self.annotations = None + if props.next(): + self.properties = props.get_object() + else: + self.properties = None + if body.next(): + self.body = body.get_object() + else: + self.body = None + + def clear(self): + """ + Clears the contents of the L{Message}. All fields will be reset to + their default values. + """ + pn_message_clear(self._msg) + self.instructions = None + self.annotations = None + self.properties = None + self.body = None + + def _is_inferred(self): + return pn_message_is_inferred(self._msg) + + def _set_inferred(self, value): + self._check(pn_message_set_inferred(self._msg, bool(value))) + + inferred = property(_is_inferred, _set_inferred, doc=""" +The inferred flag for a message indicates how the message content +is encoded into AMQP sections. If inferred is true then binary and +list values in the body of the message will be encoded as AMQP DATA +and AMQP SEQUENCE sections, respectively. If inferred is false, +then all values in the body of the message will be encoded as AMQP +VALUE sections regardless of their type. +""") + + def _is_durable(self): + return pn_message_is_durable(self._msg) + + def _set_durable(self, value): + self._check(pn_message_set_durable(self._msg, bool(value))) + + durable = property(_is_durable, _set_durable, + doc=""" +The durable property indicates that the message should be held durably +by any intermediaries taking responsibility for the message. +""") + + def _get_priority(self): + return pn_message_get_priority(self._msg) + + def _set_priority(self, value): + self._check(pn_message_set_priority(self._msg, value)) + + priority = property(_get_priority, _set_priority, + doc=""" +The priority of the message. +""") + + def _get_ttl(self): + return millis2secs(pn_message_get_ttl(self._msg)) + + def _set_ttl(self, value): + self._check(pn_message_set_ttl(self._msg, secs2millis(value))) + + ttl = property(_get_ttl, _set_ttl, + doc=""" +The time to live of the message measured in seconds. Expired messages +may be dropped. +""") + + def _is_first_acquirer(self): + return pn_message_is_first_acquirer(self._msg) + + def _set_first_acquirer(self, value): + self._check(pn_message_set_first_acquirer(self._msg, bool(value))) + + first_acquirer = property(_is_first_acquirer, _set_first_acquirer, + doc=""" +True iff the recipient is the first to acquire the message. +""") + + def _get_delivery_count(self): + return pn_message_get_delivery_count(self._msg) + + def _set_delivery_count(self, value): + self._check(pn_message_set_delivery_count(self._msg, value)) + + delivery_count = property(_get_delivery_count, _set_delivery_count, + doc=""" +The number of delivery attempts made for this message. +""") + + def _get_id(self): + return self._id.get_object() + + def _set_id(self, value): + if isinteger(value): + value = ulong(value) + self._id.rewind() + self._id.put_object(value) + + id = property(_get_id, _set_id, + doc=""" +The id of the message. +""") + + def _get_user_id(self): + return pn_message_get_user_id(self._msg) + + def _set_user_id(self, value): + self._check(pn_message_set_user_id(self._msg, value)) + + user_id = property(_get_user_id, _set_user_id, + doc=""" +The user id of the message creator. +""") + + def _get_address(self): + return utf82unicode(pn_message_get_address(self._msg)) + + def _set_address(self, value): + self._check(pn_message_set_address(self._msg, unicode2utf8(value))) + + address = property(_get_address, _set_address, + doc=""" +The address of the message. +""") + + def _get_subject(self): + return utf82unicode(pn_message_get_subject(self._msg)) + + def _set_subject(self, value): + self._check(pn_message_set_subject(self._msg, unicode2utf8(value))) + + subject = property(_get_subject, _set_subject, + doc=""" +The subject of the message. +""") + + def _get_reply_to(self): + return utf82unicode(pn_message_get_reply_to(self._msg)) + + def _set_reply_to(self, value): + self._check(pn_message_set_reply_to(self._msg, unicode2utf8(value))) + + reply_to = property(_get_reply_to, _set_reply_to, + doc=""" +The reply-to address for the message. +""") + + def _get_correlation_id(self): + return self._correlation_id.get_object() + + def _set_correlation_id(self, value): + if isinteger(value): + value = ulong(value) + self._correlation_id.rewind() + self._correlation_id.put_object(value) + + correlation_id = property(_get_correlation_id, _set_correlation_id, + doc=""" +The correlation-id for the message. +""") + + def _get_content_type(self): + return symbol(utf82unicode(pn_message_get_content_type(self._msg))) + + def _set_content_type(self, value): + self._check(pn_message_set_content_type(self._msg, unicode2utf8(value))) + + content_type = property(_get_content_type, _set_content_type, + doc=""" +The content-type of the message. +""") + + def _get_content_encoding(self): + return symbol(utf82unicode(pn_message_get_content_encoding(self._msg))) + + def _set_content_encoding(self, value): + self._check(pn_message_set_content_encoding(self._msg, unicode2utf8(value))) + + content_encoding = property(_get_content_encoding, _set_content_encoding, + doc=""" +The content-encoding of the message. +""") + + def _get_expiry_time(self): + return millis2secs(pn_message_get_expiry_time(self._msg)) + + def _set_expiry_time(self, value): + self._check(pn_message_set_expiry_time(self._msg, secs2millis(value))) + + expiry_time = property(_get_expiry_time, _set_expiry_time, + doc=""" +The expiry time of the message. +""") + + def _get_creation_time(self): + return millis2secs(pn_message_get_creation_time(self._msg)) + + def _set_creation_time(self, value): + self._check(pn_message_set_creation_time(self._msg, secs2millis(value))) + + creation_time = property(_get_creation_time, _set_creation_time, + doc=""" +The creation time of the message. +""") + + def _get_group_id(self): + return utf82unicode(pn_message_get_group_id(self._msg)) + + def _set_group_id(self, value): + self._check(pn_message_set_group_id(self._msg, unicode2utf8(value))) + + group_id = property(_get_group_id, _set_group_id, + doc=""" +The group id of the message. +""") + + def _get_group_sequence(self): + return pn_message_get_group_sequence(self._msg) + + def _set_group_sequence(self, value): + self._check(pn_message_set_group_sequence(self._msg, value)) + + group_sequence = property(_get_group_sequence, _set_group_sequence, + doc=""" +The sequence of the message within its group. +""") + + def _get_reply_to_group_id(self): + return utf82unicode(pn_message_get_reply_to_group_id(self._msg)) + + def _set_reply_to_group_id(self, value): + self._check(pn_message_set_reply_to_group_id(self._msg, unicode2utf8(value))) + + reply_to_group_id = property(_get_reply_to_group_id, _set_reply_to_group_id, + doc=""" +The group-id for any replies. +""") + + def encode(self): + self._pre_encode() + sz = 16 + while True: + err, data = pn_message_encode(self._msg, sz) + if err == PN_OVERFLOW: + sz *= 2 + continue + else: + self._check(err) + return data + + def decode(self, data): + self._check(pn_message_decode(self._msg, data)) + self._post_decode() + + def send(self, sender, tag=None): + dlv = sender.delivery(tag or sender.delivery_tag()) + encoded = self.encode() + sender.stream(encoded) + sender.advance() + if sender.snd_settle_mode == Link.SND_SETTLED: + dlv.settle() + return dlv + + def recv(self, link): + """ + Receives and decodes the message content for the current delivery + from the link. Upon success it will return the current delivery + for the link. If there is no current delivery, or if the current + delivery is incomplete, or if the link is not a receiver, it will + return None. + + @type link: Link + @param link: the link to receive a message from + @return the delivery associated with the decoded message (or None) + + """ + if link.is_sender: return None + dlv = link.current + if not dlv or dlv.partial: return None + dlv.encoded = link.recv(dlv.pending) + link.advance() + # the sender has already forgotten about the delivery, so we might + # as well too + if link.remote_snd_settle_mode == Link.SND_SETTLED: + dlv.settle() + self.decode(dlv.encoded) + return dlv + + def __repr2__(self): + props = [] + for attr in ("inferred", "address", "reply_to", "durable", "ttl", + "priority", "first_acquirer", "delivery_count", "id", + "correlation_id", "user_id", "group_id", "group_sequence", + "reply_to_group_id", "instructions", "annotations", + "properties", "body"): + value = getattr(self, attr) + if value: props.append("%s=%r" % (attr, value)) + return "Message(%s)" % ", ".join(props) + + def __repr__(self): + tmp = pn_string(None) + err = pn_inspect(self._msg, tmp) + result = pn_string_get(tmp) + pn_free(tmp) + self._check(err) + return result diff --git a/python/proton/_reactor_impl.py b/python/proton/_reactor_impl.py new file mode 100644 index 0000000000..39986ff0f3 --- /dev/null +++ b/python/proton/_reactor_impl.py @@ -0,0 +1,217 @@ +# +# 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 weakref + +from cproton import PN_INVALID_SOCKET, \ + pn_incref, pn_decref, \ + pn_handler_add, pn_handler_clear, pn_pyhandler, \ + pn_selectable_is_reading, pn_selectable_attachments, pn_selectable_set_reading, \ + pn_selectable_expired, pn_selectable_set_fd, pn_selectable_set_registered, pn_selectable_writable, \ + pn_selectable_is_writing, pn_selectable_set_deadline, pn_selectable_is_registered, pn_selectable_terminate, \ + pn_selectable_get_deadline, pn_selectable_is_terminal, pn_selectable_readable, \ + pn_selectable_release, pn_selectable_set_writing, pn_selectable_get_fd + +from ._common import millis2secs, secs2millis +from ._wrapper import Wrapper + +from . import _compat + +_DEFAULT = object() + + +class Selectable(Wrapper): + + @staticmethod + def wrap(impl): + if impl is None: + return None + else: + return Selectable(impl) + + def __init__(self, impl): + Wrapper.__init__(self, impl, pn_selectable_attachments) + + def _init(self): + pass + + def fileno(self, fd=_DEFAULT): + if fd is _DEFAULT: + return pn_selectable_get_fd(self._impl) + elif fd is None: + pn_selectable_set_fd(self._impl, PN_INVALID_SOCKET) + else: + pn_selectable_set_fd(self._impl, fd) + + def _is_reading(self): + return pn_selectable_is_reading(self._impl) + + def _set_reading(self, val): + pn_selectable_set_reading(self._impl, bool(val)) + + reading = property(_is_reading, _set_reading) + + def _is_writing(self): + return pn_selectable_is_writing(self._impl) + + def _set_writing(self, val): + pn_selectable_set_writing(self._impl, bool(val)) + + writing = property(_is_writing, _set_writing) + + def _get_deadline(self): + tstamp = pn_selectable_get_deadline(self._impl) + if tstamp: + return millis2secs(tstamp) + else: + return None + + def _set_deadline(self, deadline): + pn_selectable_set_deadline(self._impl, secs2millis(deadline)) + + deadline = property(_get_deadline, _set_deadline) + + def readable(self): + pn_selectable_readable(self._impl) + + def writable(self): + pn_selectable_writable(self._impl) + + def expired(self): + pn_selectable_expired(self._impl) + + def _is_registered(self): + return pn_selectable_is_registered(self._impl) + + def _set_registered(self, registered): + pn_selectable_set_registered(self._impl, registered) + + registered = property(_is_registered, _set_registered, + doc=""" +The registered property may be get/set by an I/O polling system to +indicate whether the fd has been registered or not. +""") + + @property + def is_terminal(self): + return pn_selectable_is_terminal(self._impl) + + def terminate(self): + pn_selectable_terminate(self._impl) + + def release(self): + pn_selectable_release(self._impl) + + +class _cadapter: + + def __init__(self, handler, on_error=None): + self.handler = handler + self.on_error = on_error + + def dispatch(self, cevent, ctype): + from ._events import Event + ev = Event.wrap(cevent, ctype) + ev.dispatch(self.handler) + + def exception(self, exc, val, tb): + if self.on_error is None: + _compat.raise_(exc, val, tb) + else: + self.on_error((exc, val, tb)) + + +class WrappedHandlersChildSurrogate: + def __init__(self, delegate): + self.handlers = [] + self.delegate = weakref.ref(delegate) + + def on_unhandled(self, method, event): + from ._events import dispatch + delegate = self.delegate() + if delegate: + dispatch(delegate, method, event) + + +class WrappedHandlersProperty(object): + def __get__(self, obj, clazz): + if obj is None: + return None + return self.surrogate(obj).handlers + + def __set__(self, obj, value): + self.surrogate(obj).handlers = value + + def surrogate(self, obj): + key = "_surrogate" + objdict = obj.__dict__ + surrogate = objdict.get(key, None) + if surrogate is None: + objdict[key] = surrogate = WrappedHandlersChildSurrogate(obj) + obj.add(surrogate) + return surrogate + + +class WrappedHandler(Wrapper): + handlers = WrappedHandlersProperty() + + @classmethod + def wrap(cls, impl, on_error=None): + if impl is None: + return None + else: + handler = cls(impl) + handler.__dict__["on_error"] = on_error + return handler + + def __init__(self, impl_or_constructor): + Wrapper.__init__(self, impl_or_constructor) + if list(self.__class__.__mro__).index(WrappedHandler) > 1: + # instantiate the surrogate + self.handlers.extend([]) + + def _on_error(self, info): + on_error = getattr(self, "on_error", None) + if on_error is None: + _compat.raise_(info[0], info[1], info[2]) + else: + on_error(info) + + def add(self, handler, on_error=None): + if handler is None: return + if on_error is None: on_error = self._on_error + impl = _chandler(handler, on_error) + pn_handler_add(self._impl, impl) + pn_decref(impl) + + def clear(self): + pn_handler_clear(self._impl) + + +def _chandler(obj, on_error=None): + if obj is None: + return None + elif isinstance(obj, WrappedHandler): + impl = obj._impl + pn_incref(impl) + return impl + else: + return pn_pyhandler(_cadapter(obj, on_error)) diff --git a/python/proton/_transport.py b/python/proton/_transport.py new file mode 100644 index 0000000000..3db007853d --- /dev/null +++ b/python/proton/_transport.py @@ -0,0 +1,524 @@ +# +# 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 + +from cproton import PN_SASL_AUTH, PN_SASL_PERM, PN_SASL_SYS, PN_SSL_RESUME_REUSED, PN_SASL_NONE, PN_SSL_SHA1, \ + PN_SSL_CERT_SUBJECT_COUNTRY_NAME, PN_SASL_OK, PN_SSL_RESUME_UNKNOWN, PN_EOS, PN_SSL_ANONYMOUS_PEER, PN_SSL_MD5, \ + PN_SSL_CERT_SUBJECT_COMMON_NAME, PN_SSL_VERIFY_PEER, PN_SSL_CERT_SUBJECT_CITY_OR_LOCALITY, PN_SSL_MODE_SERVER, \ + PN_TRACE_DRV, PN_TRACE_RAW, pn_transport, PN_SSL_SHA256, PN_TRACE_FRM, PN_SSL_MODE_CLIENT, PN_SASL_TEMP, \ + PN_SSL_SHA512, PN_SSL_CERT_SUBJECT_ORGANIZATION_UNIT, PN_OK, PN_SSL_CERT_SUBJECT_STATE_OR_PROVINCE, \ + PN_SSL_VERIFY_PEER_NAME, PN_SSL_CERT_SUBJECT_ORGANIZATION_NAME, PN_SSL_RESUME_NEW, PN_TRACE_OFF, \ + pn_transport_get_channel_max, pn_transport_capacity, pn_transport_push, pn_transport_get_user, pn_transport_tick, \ + pn_transport_set_max_frame, pn_transport_attachments, pn_transport_unbind, pn_transport_peek, \ + pn_transport_set_channel_max, pn_transport_close_tail, pn_transport_condition, pn_transport_is_encrypted, \ + pn_transport_get_frames_input, pn_transport_bind, pn_transport_closed, pn_transport_get_idle_timeout, \ + pn_transport_get_remote_idle_timeout, pn_transport_get_frames_output, pn_transport_pending, \ + pn_transport_set_pytracer, pn_transport_close_head, pn_transport_get_remote_max_frame, \ + pn_transport_is_authenticated, pn_transport_set_idle_timeout, pn_transport_log, pn_transport_get_pytracer, \ + pn_transport_require_auth, pn_transport_get_max_frame, pn_transport_set_server, pn_transport_remote_channel_max, \ + pn_transport_require_encryption, pn_transport_pop, pn_transport_connection, \ + pn_sasl, pn_sasl_set_allow_insecure_mechs, pn_sasl_outcome, pn_transport_error, pn_sasl_get_user, \ + pn_sasl_extended, pn_sasl_done, pn_sasl_get_allow_insecure_mechs, pn_sasl_allowed_mechs, \ + pn_sasl_config_name, pn_sasl_config_path, \ + pn_ssl, pn_ssl_init, pn_ssl_domain_allow_unsecured_client, pn_ssl_domain_free, \ + pn_ssl_domain, pn_transport_trace, pn_ssl_resume_status, pn_sasl_get_mech, \ + pn_ssl_domain_set_trusted_ca_db, pn_ssl_get_remote_subject_subfield, pn_ssl_present, \ + pn_ssl_get_remote_subject, pn_ssl_domain_set_credentials, pn_ssl_domain_set_peer_authentication, \ + pn_ssl_get_peer_hostname, pn_ssl_set_peer_hostname, pn_ssl_get_cipher_name, pn_ssl_get_cert_fingerprint, \ + pn_ssl_get_protocol_name, \ + pn_error_text + +from ._common import millis2secs, secs2millis, unicode2utf8, utf82unicode +from ._condition import cond2obj +from ._exceptions import EXCEPTIONS, TransportException, SessionException, SSLException, SSLUnavailable +from ._wrapper import Wrapper + + +class TraceAdapter: + + def __init__(self, tracer): + self.tracer = tracer + + def __call__(self, trans_impl, message): + self.tracer(Transport.wrap(trans_impl), message) + + +class Transport(Wrapper): + TRACE_OFF = PN_TRACE_OFF + TRACE_DRV = PN_TRACE_DRV + TRACE_FRM = PN_TRACE_FRM + TRACE_RAW = PN_TRACE_RAW + + CLIENT = 1 + SERVER = 2 + + @staticmethod + def wrap(impl): + if impl is None: + return None + else: + return Transport(_impl=impl) + + def __init__(self, mode=None, _impl=pn_transport): + Wrapper.__init__(self, _impl, pn_transport_attachments) + if mode == Transport.SERVER: + pn_transport_set_server(self._impl) + elif mode is None or mode == Transport.CLIENT: + pass + else: + raise TransportException("Cannot initialise Transport from mode: %s" % str(mode)) + + def _init(self): + self._sasl = None + self._ssl = None + + def _check(self, err): + if err < 0: + exc = EXCEPTIONS.get(err, TransportException) + raise exc("[%s]: %s" % (err, pn_error_text(pn_transport_error(self._impl)))) + else: + return err + + def _set_tracer(self, tracer): + pn_transport_set_pytracer(self._impl, TraceAdapter(tracer)) + + def _get_tracer(self): + adapter = pn_transport_get_pytracer(self._impl) + if adapter: + return adapter.tracer + else: + return None + + tracer = property(_get_tracer, _set_tracer, + doc=""" +A callback for trace logging. The callback is passed the transport and log message. +""") + + def log(self, message): + pn_transport_log(self._impl, message) + + def require_auth(self, bool): + pn_transport_require_auth(self._impl, bool) + + @property + def authenticated(self): + return pn_transport_is_authenticated(self._impl) + + def require_encryption(self, bool): + pn_transport_require_encryption(self._impl, bool) + + @property + def encrypted(self): + return pn_transport_is_encrypted(self._impl) + + @property + def user(self): + return pn_transport_get_user(self._impl) + + def bind(self, connection): + """Assign a connection to the transport""" + self._check(pn_transport_bind(self._impl, connection._impl)) + + def unbind(self): + """Release the connection""" + self._check(pn_transport_unbind(self._impl)) + + def trace(self, n): + pn_transport_trace(self._impl, n) + + def tick(self, now): + """Process any timed events (like heartbeat generation). + now = seconds since epoch (float). + """ + return millis2secs(pn_transport_tick(self._impl, secs2millis(now))) + + def capacity(self): + c = pn_transport_capacity(self._impl) + if c >= PN_EOS: + return c + else: + return self._check(c) + + def push(self, binary): + n = self._check(pn_transport_push(self._impl, binary)) + if n != len(binary): + raise OverflowError("unable to process all bytes: %s, %s" % (n, len(binary))) + + def close_tail(self): + self._check(pn_transport_close_tail(self._impl)) + + def pending(self): + p = pn_transport_pending(self._impl) + if p >= PN_EOS: + return p + else: + return self._check(p) + + def peek(self, size): + cd, out = pn_transport_peek(self._impl, size) + if cd == PN_EOS: + return None + else: + self._check(cd) + return out + + def pop(self, size): + pn_transport_pop(self._impl, size) + + def close_head(self): + self._check(pn_transport_close_head(self._impl)) + + @property + def closed(self): + return pn_transport_closed(self._impl) + + # AMQP 1.0 max-frame-size + def _get_max_frame_size(self): + return pn_transport_get_max_frame(self._impl) + + def _set_max_frame_size(self, value): + pn_transport_set_max_frame(self._impl, value) + + max_frame_size = property(_get_max_frame_size, _set_max_frame_size, + doc=""" +Sets the maximum size for received frames (in bytes). +""") + + @property + def remote_max_frame_size(self): + return pn_transport_get_remote_max_frame(self._impl) + + def _get_channel_max(self): + return pn_transport_get_channel_max(self._impl) + + def _set_channel_max(self, value): + if pn_transport_set_channel_max(self._impl, value): + raise SessionException("Too late to change channel max.") + + channel_max = property(_get_channel_max, _set_channel_max, + doc=""" +Sets the maximum channel that may be used on the transport. +""") + + @property + def remote_channel_max(self): + return pn_transport_remote_channel_max(self._impl) + + # AMQP 1.0 idle-time-out + def _get_idle_timeout(self): + return millis2secs(pn_transport_get_idle_timeout(self._impl)) + + def _set_idle_timeout(self, sec): + pn_transport_set_idle_timeout(self._impl, secs2millis(sec)) + + idle_timeout = property(_get_idle_timeout, _set_idle_timeout, + doc=""" +The idle timeout of the connection (float, in seconds). +""") + + @property + def remote_idle_timeout(self): + return millis2secs(pn_transport_get_remote_idle_timeout(self._impl)) + + @property + def frames_output(self): + return pn_transport_get_frames_output(self._impl) + + @property + def frames_input(self): + return pn_transport_get_frames_input(self._impl) + + def sasl(self): + return SASL(self) + + def ssl(self, domain=None, session_details=None): + # SSL factory (singleton for this transport) + if not self._ssl: + self._ssl = SSL(self, domain, session_details) + return self._ssl + + @property + def condition(self): + return cond2obj(pn_transport_condition(self._impl)) + + @property + def connection(self): + from . import _endpoints + return _endpoints.Connection.wrap(pn_transport_connection(self._impl)) + + +class SASLException(TransportException): + pass + + +class SASL(Wrapper): + OK = PN_SASL_OK + AUTH = PN_SASL_AUTH + SYS = PN_SASL_SYS + PERM = PN_SASL_PERM + TEMP = PN_SASL_TEMP + + @staticmethod + def extended(): + return pn_sasl_extended() + + def __init__(self, transport): + Wrapper.__init__(self, transport._impl, pn_transport_attachments) + self._sasl = pn_sasl(transport._impl) + + def _check(self, err): + if err < 0: + exc = EXCEPTIONS.get(err, SASLException) + raise exc("[%s]" % (err)) + else: + return err + + @property + def user(self): + return pn_sasl_get_user(self._sasl) + + @property + def mech(self): + return pn_sasl_get_mech(self._sasl) + + @property + def outcome(self): + outcome = pn_sasl_outcome(self._sasl) + if outcome == PN_SASL_NONE: + return None + else: + return outcome + + def allowed_mechs(self, mechs): + pn_sasl_allowed_mechs(self._sasl, unicode2utf8(mechs)) + + def _get_allow_insecure_mechs(self): + return pn_sasl_get_allow_insecure_mechs(self._sasl) + + def _set_allow_insecure_mechs(self, insecure): + pn_sasl_set_allow_insecure_mechs(self._sasl, insecure) + + allow_insecure_mechs = property(_get_allow_insecure_mechs, _set_allow_insecure_mechs, + doc=""" +Allow unencrypted cleartext passwords (PLAIN mech) +""") + + def done(self, outcome): + pn_sasl_done(self._sasl, outcome) + + def config_name(self, name): + pn_sasl_config_name(self._sasl, name) + + def config_path(self, path): + pn_sasl_config_path(self._sasl, path) + + +class SSLDomain(object): + MODE_CLIENT = PN_SSL_MODE_CLIENT + MODE_SERVER = PN_SSL_MODE_SERVER + VERIFY_PEER = PN_SSL_VERIFY_PEER + VERIFY_PEER_NAME = PN_SSL_VERIFY_PEER_NAME + ANONYMOUS_PEER = PN_SSL_ANONYMOUS_PEER + + def __init__(self, mode): + self._domain = pn_ssl_domain(mode) + if self._domain is None: + raise SSLUnavailable() + + def _check(self, err): + if err < 0: + exc = EXCEPTIONS.get(err, SSLException) + raise exc("SSL failure.") + else: + return err + + def set_credentials(self, cert_file, key_file, password): + return self._check(pn_ssl_domain_set_credentials(self._domain, + cert_file, key_file, + password)) + + def set_trusted_ca_db(self, certificate_db): + return self._check(pn_ssl_domain_set_trusted_ca_db(self._domain, + certificate_db)) + + def set_peer_authentication(self, verify_mode, trusted_CAs=None): + return self._check(pn_ssl_domain_set_peer_authentication(self._domain, + verify_mode, + trusted_CAs)) + + def allow_unsecured_client(self): + return self._check(pn_ssl_domain_allow_unsecured_client(self._domain)) + + def __del__(self): + pn_ssl_domain_free(self._domain) + + +class SSL(object): + + @staticmethod + def present(): + return pn_ssl_present() + + def _check(self, err): + if err < 0: + exc = EXCEPTIONS.get(err, SSLException) + raise exc("SSL failure.") + else: + return err + + def __new__(cls, transport, domain, session_details=None): + """Enforce a singleton SSL object per Transport""" + if transport._ssl: + # unfortunately, we've combined the allocation and the configuration in a + # single step. So catch any attempt by the application to provide what + # may be a different configuration than the original (hack) + ssl = transport._ssl + if (domain and (ssl._domain is not domain) or + session_details and (ssl._session_details is not session_details)): + raise SSLException("Cannot re-configure existing SSL object!") + else: + obj = super(SSL, cls).__new__(cls) + obj._domain = domain + obj._session_details = session_details + session_id = None + if session_details: + session_id = session_details.get_session_id() + obj._ssl = pn_ssl(transport._impl) + if obj._ssl is None: + raise SSLUnavailable() + if domain: + pn_ssl_init(obj._ssl, domain._domain, session_id) + transport._ssl = obj + return transport._ssl + + def cipher_name(self): + rc, name = pn_ssl_get_cipher_name(self._ssl, 128) + if rc: + return name + return None + + def protocol_name(self): + rc, name = pn_ssl_get_protocol_name(self._ssl, 128) + if rc: + return name + return None + + SHA1 = PN_SSL_SHA1 + SHA256 = PN_SSL_SHA256 + SHA512 = PN_SSL_SHA512 + MD5 = PN_SSL_MD5 + + CERT_COUNTRY_NAME = PN_SSL_CERT_SUBJECT_COUNTRY_NAME + CERT_STATE_OR_PROVINCE = PN_SSL_CERT_SUBJECT_STATE_OR_PROVINCE + CERT_CITY_OR_LOCALITY = PN_SSL_CERT_SUBJECT_CITY_OR_LOCALITY + CERT_ORGANIZATION_NAME = PN_SSL_CERT_SUBJECT_ORGANIZATION_NAME + CERT_ORGANIZATION_UNIT = PN_SSL_CERT_SUBJECT_ORGANIZATION_UNIT + CERT_COMMON_NAME = PN_SSL_CERT_SUBJECT_COMMON_NAME + + def get_cert_subject_subfield(self, subfield_name): + subfield_value = pn_ssl_get_remote_subject_subfield(self._ssl, subfield_name) + return subfield_value + + def get_cert_subject(self): + subject = pn_ssl_get_remote_subject(self._ssl) + return subject + + def _get_cert_subject_unknown_subfield(self): + # Pass in an unhandled enum + return self.get_cert_subject_subfield(10) + + # Convenience functions for obtaining the subfields of the subject field. + def get_cert_common_name(self): + return self.get_cert_subject_subfield(SSL.CERT_COMMON_NAME) + + def get_cert_organization(self): + return self.get_cert_subject_subfield(SSL.CERT_ORGANIZATION_NAME) + + def get_cert_organization_unit(self): + return self.get_cert_subject_subfield(SSL.CERT_ORGANIZATION_UNIT) + + def get_cert_locality_or_city(self): + return self.get_cert_subject_subfield(SSL.CERT_CITY_OR_LOCALITY) + + def get_cert_country(self): + return self.get_cert_subject_subfield(SSL.CERT_COUNTRY_NAME) + + def get_cert_state_or_province(self): + return self.get_cert_subject_subfield(SSL.CERT_STATE_OR_PROVINCE) + + def get_cert_fingerprint(self, fingerprint_length, digest_name): + rc, fingerprint_str = pn_ssl_get_cert_fingerprint(self._ssl, fingerprint_length, digest_name) + if rc == PN_OK: + return fingerprint_str + return None + + # Convenience functions for obtaining fingerprint for specific hashing algorithms + def _get_cert_fingerprint_unknown_hash_alg(self): + return self.get_cert_fingerprint(41, 10) + + def get_cert_fingerprint_sha1(self): + return self.get_cert_fingerprint(41, SSL.SHA1) + + def get_cert_fingerprint_sha256(self): + # sha256 produces a fingerprint that is 64 characters long + return self.get_cert_fingerprint(65, SSL.SHA256) + + def get_cert_fingerprint_sha512(self): + # sha512 produces a fingerprint that is 128 characters long + return self.get_cert_fingerprint(129, SSL.SHA512) + + def get_cert_fingerprint_md5(self): + return self.get_cert_fingerprint(33, SSL.MD5) + + @property + def remote_subject(self): + return pn_ssl_get_remote_subject(self._ssl) + + RESUME_UNKNOWN = PN_SSL_RESUME_UNKNOWN + RESUME_NEW = PN_SSL_RESUME_NEW + RESUME_REUSED = PN_SSL_RESUME_REUSED + + def resume_status(self): + return pn_ssl_resume_status(self._ssl) + + def _set_peer_hostname(self, hostname): + self._check(pn_ssl_set_peer_hostname(self._ssl, unicode2utf8(hostname))) + + def _get_peer_hostname(self): + err, name = pn_ssl_get_peer_hostname(self._ssl, 1024) + self._check(err) + return utf82unicode(name) + + peer_hostname = property(_get_peer_hostname, _set_peer_hostname, + doc=""" +Manage the expected name of the remote peer. Used to authenticate the remote. +""") + + +class SSLSessionDetails(object): + """ Unique identifier for the SSL session. Used to resume previous session on a new + SSL connection. + """ + + def __init__(self, session_id): + self._session_id = session_id + + def get_session_id(self): + return self._session_id diff --git a/python/proton/_url.py b/python/proton/_url.py new file mode 100644 index 0000000000..b4a9a6ab9b --- /dev/null +++ b/python/proton/_url.py @@ -0,0 +1,161 @@ +# +# 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 socket + +from cproton import pn_url, pn_url_free, pn_url_parse, pn_url_str, pn_url_get_port, pn_url_get_scheme, \ + pn_url_get_host, pn_url_get_username, pn_url_get_password, pn_url_get_path, pn_url_set_scheme, pn_url_set_host, \ + pn_url_set_username, pn_url_set_password, pn_url_set_port, pn_url_set_path + +from ._common import unicode2utf8 + + +class Url(object): + """ + Simple URL parser/constructor, handles URLs of the form: + + ://:@:/ + + All components can be None if not specified in the URL string. + + The port can be specified as a service name, e.g. 'amqp' in the + URL string but Url.port always gives the integer value. + + Warning: The placement of user and password in URLs is not + recommended. It can result in credentials leaking out in program + logs. Use connection configuration attributes instead. + + @ivar scheme: Url scheme e.g. 'amqp' or 'amqps' + @ivar user: Username + @ivar password: Password + @ivar host: Host name, ipv6 literal or ipv4 dotted quad. + @ivar port: Integer port. + @ivar host_port: Returns host:port + """ + + AMQPS = "amqps" + AMQP = "amqp" + + class Port(int): + """An integer port number that can be constructed from a service name string""" + + def __new__(cls, value): + """@param value: integer port number or string service name.""" + port = super(Url.Port, cls).__new__(cls, cls._port_int(value)) + setattr(port, 'name', str(value)) + return port + + def __eq__(self, x): + return str(self) == x or int(self) == x + + def __ne__(self, x): + return not self == x + + def __str__(self): + return str(self.name) + + @staticmethod + def _port_int(value): + """Convert service, an integer or a service name, into an integer port number.""" + try: + return int(value) + except ValueError: + try: + return socket.getservbyname(value) + except socket.error: + # Not every system has amqp/amqps defined as a service + if value == Url.AMQPS: + return 5671 + elif value == Url.AMQP: + return 5672 + else: + raise ValueError("Not a valid port number or service name: '%s'" % value) + + def __init__(self, url=None, defaults=True, **kwargs): + """ + @param url: URL string to parse. + @param defaults: If true, fill in missing default values in the URL. + If false, you can fill them in later by calling self.defaults() + @param kwargs: scheme, user, password, host, port, path. + If specified, replaces corresponding part in url string. + """ + if url: + self._url = pn_url_parse(unicode2utf8(str(url))) + if not self._url: raise ValueError("Invalid URL '%s'" % url) + else: + self._url = pn_url() + for k in kwargs: # Let kwargs override values parsed from url + getattr(self, k) # Check for invalid kwargs + setattr(self, k, kwargs[k]) + if defaults: self.defaults() + + class PartDescriptor(object): + def __init__(self, part): + self.getter = globals()["pn_url_get_%s" % part] + self.setter = globals()["pn_url_set_%s" % part] + + def __get__(self, obj, type=None): return self.getter(obj._url) + + def __set__(self, obj, value): return self.setter(obj._url, str(value)) + + scheme = PartDescriptor('scheme') + username = PartDescriptor('username') + password = PartDescriptor('password') + host = PartDescriptor('host') + path = PartDescriptor('path') + + def _get_port(self): + portstr = pn_url_get_port(self._url) + return portstr and Url.Port(portstr) + + def _set_port(self, value): + if value is None: + pn_url_set_port(self._url, None) + else: + pn_url_set_port(self._url, str(Url.Port(value))) + + port = property(_get_port, _set_port) + + def __str__(self): + return pn_url_str(self._url) + + def __repr__(self): + return "Url(%s://%s/%s)" % (self.scheme, self.host, self.path) + + def __eq__(self, x): + return str(self) == str(x) + + def __ne__(self, x): + return not self == x + + def __del__(self): + pn_url_free(self._url) + del self._url + + def defaults(self): + """ + Fill in missing values (scheme, host or port) with defaults + @return: self + """ + self.scheme = self.scheme or self.AMQP + self.host = self.host or '0.0.0.0' + self.port = self.port or self.Port(self.scheme) + return self diff --git a/python/proton/wrapper.py b/python/proton/_wrapper.py similarity index 88% rename from python/proton/wrapper.py rename to python/proton/_wrapper.py index f009de5285..805ecb159c 100644 --- a/python/proton/wrapper.py +++ b/python/proton/_wrapper.py @@ -16,7 +16,12 @@ # specific language governing permissions and limitations # under the License. # -from cproton import * + +from cproton import pn_incref, pn_decref, \ + pn_py2void, pn_void2py, \ + pn_record_get, pn_record_def, pn_record_set, \ + PN_PYREF + class EmptyAttrs: @@ -29,8 +34,10 @@ def __getitem__(self, name): def __setitem__(self, name, value): raise TypeError("does not support item assignment") + EMPTY_ATTRS = EmptyAttrs() + class Wrapper(object): def __init__(self, impl_or_constructor, get_context=None): @@ -43,7 +50,8 @@ def __init__(self, impl_or_constructor, get_context=None): self.__dict__["_attrs"] = EMPTY_ATTRS self.__dict__["_record"] = None from proton import ProtonException - raise ProtonException("Wrapper failed to create wrapped object. Check for file descriptor or memory exhaustion.") + raise ProtonException( + "Wrapper failed to create wrapped object. Check for file descriptor or memory exhaustion.") init = True else: # we are wrapping an existing object @@ -104,8 +112,8 @@ def __del__(self): def __repr__(self): return '<%s.%s 0x%x ~ 0x%x>' % (self.__class__.__module__, - self.__class__.__name__, - id(self), addressof(self._impl)) + self.__class__.__name__, + id(self), addressof(self._impl)) PYCTX = int(pn_py2void(Wrapper)) diff --git a/python/proton/handlers.py b/python/proton/handlers.py index 76c9e515ab..1e61f44501 100644 --- a/python/proton/handlers.py +++ b/python/proton/handlers.py @@ -16,28 +16,35 @@ # specific language governing permissions and limitations # under the License. # -import heapq, logging, os, re, socket, time, types, weakref -from proton import dispatch, generate_uuid, PN_ACCEPTED, SASL, symbol, ulong, Url -from proton import Collector, Connection, Delivery, Described, Endpoint, Event, Link, Terminus, Timeout -from proton import Message, Handler, ProtonException, Transport, TransportException, ConnectionException +from __future__ import absolute_import + +import logging +import time +import weakref from select import select +from proton import Delivery, Endpoint +from proton import Message, Handler, ProtonException +from ._events import dispatch + log = logging.getLogger("proton") + class OutgoingMessageHandler(Handler): """ A utility for simpler and more intuitive handling of delivery events related to outgoing i.e. sent messages. """ + def __init__(self, auto_settle=True, delegate=None): self.auto_settle = auto_settle self.delegate = delegate def on_link_flow(self, event): if event.link.is_sender and event.link.credit \ - and event.link.state & Endpoint.LOCAL_ACTIVE \ - and event.link.state & Endpoint.REMOTE_ACTIVE : + and event.link.state & Endpoint.LOCAL_ACTIVE \ + and event.link.state & Endpoint.REMOTE_ACTIVE: self.on_sendable(event) def on_delivery(self, event): @@ -94,23 +101,27 @@ def on_settled(self, event): if self.delegate != None: dispatch(self.delegate, 'on_settled', event) + def recv_msg(delivery): msg = Message() msg.decode(delivery.link.recv(delivery.pending)) delivery.link.advance() return msg + class Reject(ProtonException): - """ - An exception that indicate a message should be rejected - """ - pass + """ + An exception that indicate a message should be rejected + """ + pass + class Release(ProtonException): - """ - An exception that indicate a message should be rejected - """ - pass + """ + An exception that indicate a message should be rejected + """ + pass + class Acking(object): def accept(self, delivery): @@ -146,6 +157,7 @@ def settle(self, delivery, state=None): delivery.update(state) delivery.settle() + class IncomingMessageHandler(Handler, Acking): """ A utility for simpler and more intuitive handling of delivery @@ -202,6 +214,7 @@ def on_aborted(self, event): if self.delegate != None: dispatch(self.delegate, 'on_aborted', event) + class EndpointStateHandler(Handler): """ A utility that exposes 'endpoint' events i.e. the open/close for @@ -272,7 +285,7 @@ def on_connection_remote_close(self, event): return self.on_connection_error(event) elif self.is_local_closed(event.connection): - self.on_connection_closed(event) + self.on_connection_closed(event) else: self.on_connection_closing(event) event.connection.close() @@ -391,12 +404,14 @@ def on_transport_closed(self, event): if self.delegate != None and event.connection and self.is_local_open(event.connection): dispatch(self.delegate, 'on_disconnected', event) + class MessagingHandler(Handler, Acking): """ A general purpose handler that makes the proton-c events somewhat simpler to deal with and/or avoids repetitive tasks for common use cases. """ + def __init__(self, prefetch=10, auto_accept=True, auto_settle=True, peer_close_is_error=False): self.handlers = [] if prefetch: @@ -414,7 +429,8 @@ def on_transport_error(self, event): """ if event.transport.condition: if event.transport.condition.info: - log.error("%s: %s: %s" % (event.transport.condition.name, event.transport.condition.description, event.transport.condition.info)) + log.error("%s: %s: %s" % ( + event.transport.condition.name, event.transport.condition.description, event.transport.condition.info)) else: log.error("%s: %s" % (event.transport.condition.name, event.transport.condition.description)) if event.transport.condition.name in self.fatal_conditions: @@ -455,36 +471,43 @@ def on_start(self, event): Called when the event loop starts. (Just an alias for on_reactor_init) """ pass + def on_connection_closed(self, event): """ Called when the connection is closed. """ pass + def on_session_closed(self, event): """ Called when the session is closed. """ pass + def on_link_closed(self, event): """ Called when the link is closed. """ pass + def on_connection_closing(self, event): """ Called when the peer initiates the closing of the connection. """ pass + def on_session_closing(self, event): """ Called when the peer initiates the closing of the session. """ pass + def on_link_closing(self, event): """ Called when the peer initiates the closing of the link. """ pass + def on_disconnected(self, event): """ Called when the socket is disconnected. @@ -525,6 +548,7 @@ def on_settled(self, event): retransmitted. """ pass + def on_message(self, event): """ Called when a message is received. The message itself can be @@ -535,11 +559,13 @@ def on_message(self, event): """ pass + class TransactionHandler(object): """ The interface for transaction handlers, i.e. objects that want to be notified of state changes related to a transaction. """ + def on_transaction_declared(self, event): pass @@ -555,6 +581,7 @@ def on_transaction_declare_failed(self, event): def on_transaction_commit_failed(self, event): pass + class TransactionalClientHandler(MessagingHandler, TransactionHandler): """ An extension to the MessagingHandler for applications using @@ -570,24 +597,29 @@ def accept(self, delivery, transaction=None): else: super(TransactionalClientHandler, self).accept(delivery) -from proton import WrappedHandler + +from ._events import WrappedHandler from cproton import pn_flowcontroller, pn_handshaker, pn_iohandler + class CFlowController(WrappedHandler): def __init__(self, window=1024): WrappedHandler.__init__(self, lambda: pn_flowcontroller(window)) + class CHandshaker(WrappedHandler): def __init__(self): WrappedHandler.__init__(self, pn_handshaker) + class IOHandler(WrappedHandler): def __init__(self): WrappedHandler.__init__(self, pn_iohandler) + class PythonIO: def __init__(self): diff --git a/python/proton/reactor.py b/python/proton/reactor.py index d5d5183372..ccdbf94f9a 100644 --- a/python/proton/reactor.py +++ b/python/proton/reactor.py @@ -17,22 +17,35 @@ # specific language governing permissions and limitations # under the License. # -import logging, os, socket, time, types -from heapq import heappush, heappop, nsmallest +from __future__ import absolute_import +import os +import logging import traceback -from proton import Collector, Connection, ConnectionException, Delivery, Described, dispatch -from proton import Endpoint, Event, EventBase, EventType, generate_uuid, Handler, Link, Message -from proton import ProtonException, PN_ACCEPTED, PN_PYREF, SASL, Session, SSL, SSLDomain, SSLUnavailable, symbol -from proton import Terminus, Timeout, Transport, TransportException, ulong, Url -from select import select +from proton import Connection, Delivery, Described +from proton import Endpoint, EventType, Handler, Link, Message +from proton import Session, SSL, SSLDomain, SSLUnavailable, symbol +from proton import Terminus, Transport, ulong, Url from proton.handlers import OutgoingMessageHandler -from proton import unicode2utf8, utf82unicode -from proton import WrappedHandler, _chandler, secs2millis, millis2secs, timeout2millis, millis2timeout, Selectable -from .wrapper import Wrapper, PYCTX -from cproton import * +from proton import generate_uuid + +from ._common import isstring, secs2millis, millis2secs, unicode2utf8, utf82unicode + +from ._events import EventBase +from ._reactor_impl import Selectable, WrappedHandler, _chandler +from ._wrapper import Wrapper, PYCTX + +from cproton import PN_MILLIS_MAX, PN_PYREF, PN_ACCEPTED, \ + pn_reactor_stop, pn_selectable_attachments, pn_reactor_quiesced, pn_reactor_acceptor, \ + pn_record_set_handler, pn_collector_put, pn_reactor_get_timeout, pn_task_cancel, pn_acceptor_set_ssl_domain, \ + pn_record_get, pn_reactor_selectable, pn_task_attachments, pn_reactor_schedule, pn_acceptor_close, pn_py2void, \ + pn_reactor_error, pn_reactor_attachments, pn_reactor_get_global_handler, pn_reactor_process, pn_reactor, \ + pn_reactor_set_handler, pn_reactor_set_global_handler, pn_reactor_yield, pn_error_text, pn_reactor_connection, \ + pn_cast_pn_reactor, pn_reactor_get_connection_address, pn_reactor_update, pn_reactor_collector, pn_void2py, \ + pn_reactor_start, pn_reactor_set_connection_host, pn_cast_pn_task, pn_decref, pn_reactor_set_timeout, \ + pn_reactor_mark, pn_reactor_get_handler, pn_reactor_wakeup from . import _compat @@ -40,6 +53,17 @@ log = logging.getLogger("proton") + +def _timeout2millis(secs): + if secs is None: return PN_MILLIS_MAX + return secs2millis(secs) + + +def _millis2timeout(millis): + if millis == PN_MILLIS_MAX: return None + return millis2secs(millis) + + class Task(Wrapper): @staticmethod @@ -58,6 +82,7 @@ def _init(self): def cancel(self): pn_task_cancel(self._impl) + class Acceptor(Wrapper): def __init__(self, impl): @@ -69,6 +94,7 @@ def set_ssl_domain(self, ssl_domain): def close(self): pn_acceptor_close(self._impl) + class Reactor(Wrapper): @staticmethod @@ -95,11 +121,12 @@ def _init(self): # error will always be generated from a callback from this reactor. # Needed to prevent reference cycles and be compatible with wrappers. class ErrorDelegate(object): - def __init__(self, reactor): - self.reactor_impl = reactor._impl - def on_error(self, info): - ractor = Reactor.wrap(self.reactor_impl) - ractor.on_error(info) + def __init__(self, reactor): + self.reactor_impl = reactor._impl + + def on_error(self, info): + ractor = Reactor.wrap(self.reactor_impl) + ractor.on_error(info) def on_error_delegate(self): return Reactor.ErrorDelegate(self).on_error @@ -119,10 +146,10 @@ def _set_global(self, handler): global_handler = property(_get_global, _set_global) def _get_timeout(self): - return millis2timeout(pn_reactor_get_timeout(self._impl)) + return _millis2timeout(pn_reactor_get_timeout(self._impl)) def _set_timeout(self, secs): - return pn_reactor_set_timeout(self._impl, timeout2millis(secs)) + return pn_reactor_set_timeout(self._impl, _timeout2millis(secs)) timeout = property(_get_timeout, _set_timeout) @@ -244,7 +271,9 @@ def update(self, sel): def push_event(self, obj, etype): pn_collector_put(pn_reactor_collector(self._impl), PN_PYREF, pn_py2void(obj), etype.number) -from proton import wrappers as _wrappers + +from ._events import wrappers as _wrappers + _wrappers["pn_reactor"] = lambda x: Reactor.wrap(pn_cast_pn_reactor(x)) _wrappers["pn_task"] = lambda x: Task.wrap(pn_cast_pn_task(x)) @@ -258,6 +287,7 @@ class EventInjector(object): it. The close() method should be called when it is no longer needed, to allow the event loop to end if needed. """ + def __init__(self): self.queue = queue.Queue() self.pipe = os.pipe() @@ -305,6 +335,7 @@ class ApplicationEvent(EventBase): Application defined event, which can optionally be associated with an engine object and or an arbitrary subject """ + def __init__(self, typename, connection=None, session=None, link=None, delivery=None, subject=None): super(ApplicationEvent, self).__init__(PN_PYREF, self, EventType(typename)) self.connection = connection @@ -323,10 +354,12 @@ def __repr__(self): objects = [self.connection, self.session, self.link, self.delivery, self.subject] return "%s(%s)" % (self.type, ", ".join([str(o) for o in objects if o is not None])) + class Transaction(object): """ Class to track state of an AMQP 1.0 transaction. """ + def __init__(self, txn_ctrl, handler, settle_before_discharge=False): self.txn_ctrl = txn_ctrl self.handler = handler @@ -397,7 +430,7 @@ def handle_outcome(self, event): if event.delivery.remote_state == Delivery.REJECTED: if not self.failed: self.handler.on_transaction_commit_failed(event) - self._release_pending() # make this optional? + self._release_pending() # make this optional? else: if self.failed: self.handler.on_transaction_aborted(event) @@ -406,16 +439,19 @@ def handle_outcome(self, event): self.handler.on_transaction_committed(event) self._clear_pending() + class LinkOption(object): """ Abstract interface for link configuration options """ + def apply(self, link): """ Subclasses will implement any configuration logic in this method """ pass + def test(self, link): """ Subclasses can override this to selectively apply an option @@ -423,23 +459,30 @@ def test(self, link): """ return True + class AtMostOnce(LinkOption): def apply(self, link): link.snd_settle_mode = Link.SND_SETTLED + class AtLeastOnce(LinkOption): def apply(self, link): link.snd_settle_mode = Link.SND_UNSETTLED link.rcv_settle_mode = Link.RCV_FIRST + class SenderOption(LinkOption): def apply(self, sender): pass + def test(self, link): return link.is_sender + class ReceiverOption(LinkOption): def apply(self, receiver): pass + def test(self, link): return link.is_receiver + class DynamicNodeProperties(LinkOption): def __init__(self, props={}): self.properties = {} @@ -455,6 +498,7 @@ def apply(self, link): else: link.target.properties.put_dict(self.properties) + class Filter(ReceiverOption): def __init__(self, filter_set={}): self.filter_set = filter_set @@ -462,26 +506,32 @@ def __init__(self, filter_set={}): def apply(self, receiver): receiver.source.filter.put_dict(self.filter_set) + class Selector(Filter): """ Configures a link with a message selector filter """ + def __init__(self, value, name='selector'): super(Selector, self).__init__({symbol(name): Described(symbol('apache.org:selector-filter:string'), value)}) + class DurableSubscription(ReceiverOption): def apply(self, receiver): receiver.source.durability = Terminus.DELIVERIES receiver.source.expiry_policy = Terminus.EXPIRE_NEVER + class Move(ReceiverOption): def apply(self, receiver): receiver.source.distribution_mode = Terminus.DIST_MODE_MOVE + class Copy(ReceiverOption): def apply(self, receiver): receiver.source.distribution_mode = Terminus.DIST_MODE_COPY + def _apply_link_options(options, link): if options: if isinstance(options, list): @@ -490,6 +540,7 @@ def _apply_link_options(options, link): else: if options.test(link): options.apply(link) + def _create_session(connection, handler=None): session = connection.session() session.open() @@ -502,6 +553,7 @@ def _get_attr(target, name): else: return None + class SessionPerConnection(object): def __init__(self): self._default_session = None @@ -511,11 +563,13 @@ def session(self, connection): self._default_session = _create_session(connection) return self._default_session + class GlobalOverrides(object): """ Internal handler that triggers the necessary socket connect for an opened connection. """ + def __init__(self, base): self.base = base @@ -527,11 +581,13 @@ def _override(self, event): conn = event.connection return conn and hasattr(conn, '_overrides') and event.dispatch(conn._overrides) + class Connector(Handler): """ Internal handler that triggers the necessary socket connect for an opened connection. """ + def __init__(self, connection): self.connection = connection self.address = None @@ -548,7 +604,7 @@ def __init__(self, connection): self.max_frame_size = None def _connect(self, connection, reactor): - assert(reactor is not None) + assert (reactor is not None) url = self.address.next() reactor.set_connection_host(connection, url.host, str(url.port)) # if virtual-host not set, use host from address as default @@ -615,11 +671,13 @@ def on_transport_closed(self, event): def on_timer_task(self, event): self._connect(self.connection, event.reactor) + class Backoff(object): """ A reconnect strategy involving an increasing delay between retries, up to a maximum or 10 seconds. """ + def __init__(self): self.delay = 0 @@ -631,9 +689,10 @@ def next(self): if current == 0: self.delay = 0.1 else: - self.delay = min(10, 2*current) + self.delay = min(10, 2 * current) return current + class Urls(object): def __init__(self, values): self.values = [Url(v) for v in values] @@ -649,6 +708,7 @@ def next(self): self.i = iter(self.values) return next(self.i) + class SSLConfig(object): def __init__(self): self.client = SSLDomain(SSLDomain.MODE_CLIENT) @@ -670,6 +730,7 @@ class Container(Reactor): an extension to the Reactor class that adds convenience methods for creating connections and sender- or receiver- links. """ + def __init__(self, *handlers, **kwargs): super(Container, self).__init__(*handlers, **kwargs) if "impl" not in kwargs: @@ -687,7 +748,8 @@ def __init__(self, *handlers, **kwargs): self.password = None Wrapper.__setattr__(self, 'subclass', self.__class__) - def connect(self, url=None, urls=None, address=None, handler=None, reconnect=None, heartbeat=None, ssl_domain=None, **kwargs): + def connect(self, url=None, urls=None, address=None, handler=None, reconnect=None, heartbeat=None, ssl_domain=None, + **kwargs): """ Initiates the establishment of an AMQP connection. Returns an instance of proton.Connection. @@ -748,10 +810,14 @@ def connect(self, url=None, urls=None, address=None, handler=None, reconnect=Non connector.max_frame_size = kwargs.get('max_frame_size') conn._overrides = connector - if url: connector.address = Urls([url]) - elif urls: connector.address = Urls(urls) - elif address: connector.address = address - else: raise ValueError("One of url, urls or address required") + if url: + connector.address = Urls([url]) + elif urls: + connector.address = Urls(urls) + elif address: + connector.address = address + else: + raise ValueError("One of url, urls or address required") if heartbeat: connector.heartbeat = heartbeat if reconnect: @@ -761,15 +827,19 @@ def connect(self, url=None, urls=None, address=None, handler=None, reconnect=Non # use container's default client domain if none specified. This is # only necessary of the URL specifies the "amqps:" scheme connector.ssl_domain = ssl_domain or (self.ssl and self.ssl.client) - conn._session_policy = SessionPerConnection() #todo: make configurable + conn._session_policy = SessionPerConnection() # todo: make configurable conn.open() return conn def _get_id(self, container, remote, local): - if local and remote: "%s-%s-%s" % (container, remote, local) - elif local: return "%s-%s" % (container, local) - elif remote: return "%s-%s" % (container, remote) - else: return "%s-%s" % (container, str(generate_uuid())) + if local and remote: + "%s-%s-%s" % (container, remote, local) + elif local: + return "%s-%s" % (container, local) + elif remote: + return "%s-%s" % (container, remote) + else: + return "%s-%s" % (container, str(generate_uuid())) def _get_session(self, context): if isinstance(context, Url): @@ -806,7 +876,7 @@ def create_sender(self, context, target=None, source=None, name=None, handler=No Various LinkOptions can be specified to further control the attachment. """ - if isinstance(context, _compat.string_types): + if isstring(context): context = Url(context) if isinstance(context, Url) and not target: target = context.path @@ -847,7 +917,7 @@ def create_receiver(self, context, source=None, target=None, name=None, dynamic= Various LinkOptions can be specified to further control the attachment. """ - if isinstance(context, _compat.string_types): + if isstring(context): context = Url(context) if isinstance(context, Url) and not source: source = context.path diff --git a/python/proton/utils.py b/python/proton/utils.py index 1d052d0b7e..c6f8cb4153 100644 --- a/python/proton/utils.py +++ b/python/proton/utils.py @@ -38,7 +38,8 @@ def _waitForClose(self, timeout=1): self.connection.wait(lambda: self.link.state & Endpoint.REMOTE_CLOSED, timeout=timeout, msg="Opening link %s" % self.link.name) - except Timeout as e: pass + except Timeout as e: + pass self._checkClosed() def _checkClosed(self): @@ -53,31 +54,37 @@ def close(self): msg="Closing link %s" % self.link.name) # Access to other link attributes. - def __getattr__(self, name): return getattr(self.link, name) + def __getattr__(self, name): + return getattr(self.link, name) + class SendException(ProtonException): """ Exception used to indicate an exceptional state/condition on a send request """ + def __init__(self, state): self.state = state + def _is_settled(delivery): return delivery.settled or delivery.link.snd_settle_mode == Link.SND_SETTLED + class BlockingSender(BlockingLink): def __init__(self, connection, sender): super(BlockingSender, self).__init__(connection, sender) if self.link.target and self.link.target.address and self.link.target.address != self.link.remote_target.address: - #this may be followed by a detach, which may contain an error condition, so wait a little... + # this may be followed by a detach, which may contain an error condition, so wait a little... self._waitForClose() - #...but close ourselves if peer does not + # ...but close ourselves if peer does not self.link.close() raise LinkException("Failed to open sender %s, target does not match" % self.link.name) def send(self, msg, timeout=False, error_states=None): delivery = self.link.send(msg) - self.connection.wait(lambda: _is_settled(delivery), msg="Sending on sender %s" % self.link.name, timeout=timeout) + self.connection.wait(lambda: _is_settled(delivery), msg="Sending on sender %s" % self.link.name, + timeout=timeout) if delivery.link.snd_settle_mode != Link.SND_SETTLED: delivery.settle() bad = error_states @@ -87,6 +94,7 @@ def send(self, msg, timeout=False, error_states=None): raise SendException(delivery.remote_state) return delivery + class Fetcher(MessagingHandler): def __init__(self, connection, prefetch): super(Fetcher, self).__init__(prefetch=prefetch, auto_accept=False) @@ -96,7 +104,7 @@ def __init__(self, connection, prefetch): def on_message(self, event): self.incoming.append((event.message, event.delivery)) - self.connection.container.yield_() # Wake up the wait() loop to handle the message. + self.connection.container.yield_() # Wake up the wait() loop to handle the message. def on_link_error(self, event): if event.link.state & Endpoint.LOCAL_ACTIVE: @@ -129,9 +137,9 @@ class BlockingReceiver(BlockingLink): def __init__(self, connection, receiver, fetcher, credit=1): super(BlockingReceiver, self).__init__(connection, receiver) if self.link.source and self.link.source.address and self.link.source.address != self.link.remote_source.address: - #this may be followed by a detach, which may contain an error condition, so wait a little... + # this may be followed by a detach, which may contain an error condition, so wait a little... self._waitForClose() - #...but close ourselves if peer does not + # ...but close ourselves if peer does not self.link.close() raise LinkException("Failed to open receiver %s, source does not match" % self.link.name) if credit: receiver.flow(credit) @@ -151,7 +159,8 @@ def receive(self, timeout=False): raise Exception("Can't call receive on this receiver as a handler was provided") if not self.link.credit: self.link.flow(1) - self.connection.wait(lambda: self.fetcher.has_message, msg="Receiving on receiver %s" % self.link.name, timeout=timeout) + self.connection.wait(lambda: self.fetcher.has_message, msg="Receiving on receiver %s" % self.link.name, + timeout=timeout) return self.fetcher.pop() def accept(self): @@ -210,6 +219,7 @@ class BlockingConnection(Handler): object operations are enclosed in a try block and that close() is always executed on exit. """ + def __init__(self, url, timeout=None, container=None, ssl_domain=None, heartbeat=None, **kwargs): self.disconnected = False self.timeout = timeout or 60 @@ -221,7 +231,8 @@ def __init__(self, url, timeout=None, container=None, ssl_domain=None, heartbeat self.closing = False failed = True try: - self.conn = self.container.connect(url=self.url, handler=self, ssl_domain=ssl_domain, reconnect=False, heartbeat=heartbeat, **kwargs) + self.conn = self.container.connect(url=self.url, handler=self, ssl_domain=ssl_domain, reconnect=False, + heartbeat=heartbeat, **kwargs) self.wait(lambda: not (self.conn.state & Endpoint.REMOTE_UNINIT), msg="Opening connection") failed = False @@ -230,7 +241,8 @@ def __init__(self, url, timeout=None, container=None, ssl_domain=None, heartbeat self.close() def create_sender(self, address, handler=None, name=None, options=None): - return BlockingSender(self, self.container.create_sender(self.conn, address, name=name, handler=handler, options=options)) + return BlockingSender(self, self.container.create_sender(self.conn, address, name=name, handler=handler, + options=options)) def create_receiver(self, address, credit=None, dynamic=False, handler=None, name=None, options=None): prefetch = credit @@ -241,7 +253,9 @@ def create_receiver(self, address, credit=None, dynamic=False, handler=None, nam else: fetcher = Fetcher(self, credit) return BlockingReceiver( - self, self.container.create_receiver(self.conn, address, name=name, dynamic=dynamic, handler=handler or fetcher, options=options), fetcher, credit=prefetch) + self, + self.container.create_receiver(self.conn, address, name=name, dynamic=dynamic, handler=handler or fetcher, + options=options), fetcher, credit=prefetch) def close(self): # TODO: provide stronger interrupt protection on cleanup. See PEP 419 @@ -259,8 +273,8 @@ def close(self): # Nothing left to block on. Allow reactor to clean up. self.run() self.conn = None - self.container.global_handler = None # break circular ref: container to cadapter.on_error - pn_collector_release(pn_reactor_collector(self.container._impl)) # straggling event may keep reactor alive + self.container.global_handler = None # break circular ref: container to cadapter.on_error + pn_collector_release(pn_reactor_collector(self.container._impl)) # straggling event may keep reactor alive self.container = None def _is_closed(self): @@ -294,7 +308,7 @@ def wait(self, condition, timeout=False, msg=None): self.container.timeout = container_timeout if self.disconnected or self._is_closed(): self.container.stop() - self.conn.handler = None # break cyclical reference + self.conn.handler = None # break cyclical reference if self.disconnected and not self._is_closed(): raise ConnectionException( "Connection %s disconnected: %s" % (self.url, self.disconnected)) @@ -320,6 +334,7 @@ def on_transport_head_closed(self, event): def on_transport_closed(self, event): self.disconnected = event.transport.condition or "unknown" + class AtomicCount(object): def __init__(self, start=0, step=1): """Thread-safe atomic counter. Start at start, increment by step.""" @@ -334,6 +349,7 @@ def next(self): self.lock.release() return result + class SyncRequestResponse(IncomingMessageHandler): """ Implementation of the synchronous request-response (aka RPC) pattern. @@ -374,12 +390,14 @@ def call(self, request): request.reply_to = self.reply_to request.correlation_id = correlation_id = str(self.correlation_id.next()) self.sender.send(request) + def wakeup(): return self.response and (self.response.correlation_id == correlation_id) + self.connection.wait(wakeup, msg="Waiting for response") response = self.response - self.response = None # Ready for next response. - self.receiver.flow(1) # Set up credit for the next response. + self.response = None # Ready for next response. + self.receiver.flow(1) # Set up credit for the next response. return response @property @@ -390,4 +408,4 @@ def reply_to(self): def on_message(self, event): """Called when we receive a message for our receiver.""" self.response = event.message - self.connection.container.yield_() # Wake up the wait() loop to handle the message. + self.connection.container.yield_() # Wake up the wait() loop to handle the message.