# # 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 module defines a suite of APIs that implement the AMQP 1.0 protocol. The proton APIs consist of the following classes: - L{Messenger} -- A messaging endpoint. - L{Message} -- A class for creating and/or accessing AMQP message content. - L{Data} -- A class for creating and/or accessing arbitrary AMQP encoded data. """ from cproton import * import weakref, re, socket try: import uuid 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, socket, time rand = random.Random() rand.seed((os.getpid(), time.time(), socket.gethostname())) def random_uuid(): bytes = [rand.randint(0, 255) for i in xrange(16)] # From RFC4122, the version bits are set to 0100 bytes[7] &= 0x0F bytes[7] |= 0x40 # From RFC4122, the top two bits of byte 8 get set to 01 bytes[8] &= 0x3F bytes[8] |= 0x80 return "".join(map(chr, bytes)) def uuid4(): return uuid.UUID(bytes=random_uuid()) def generate_uuid(): return uuid.uuid4() try: bytes() except NameError: bytes = str VERSION_MAJOR = PN_VERSION_MAJOR VERSION_MINOR = PN_VERSION_MINOR 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 indicaes that a blocking operation was interrupted. """ pass class MessengerException(ProtonException): """ The root of the messenger exception hierarchy. All exceptions generated by the messenger class derive from this exception. """ pass class MessageException(ProtonException): """ The MessageException class is the root of the message exception hierarhcy. 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") 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_PENDING: PENDING, PN_STATUS_SETTLED: SETTLED, PN_STATUS_UNKNOWN: None } AUTOMATIC = Constant("AUTOMATIC") MANUAL = Constant("MANUAL") class Messenger(object): """ The L{Messenger} class defines a high level interface for sending and receiving L{Messages}. Every L{Messenger} contains a single logical queue of incoming messages and a single logical queue of outgoing messages. These messages in these queues may be destined for, or originate from, a variety of addresses. The messenger interface is single-threaded. All methods except one (L{interrupt}) are intended to be used from within the messenger thread. Address Syntax ============== An address has the following form:: [ amqp[s]:// ] [user[:password]@] domain [/[name]] Where domain can be one of:: host | host:port | ip | ip:port | name The following are valid examples of addresses: - example.org - example.org:1234 - amqp://example.org - amqps://example.org - example.org/incoming - amqps://example.org/outgoing - amqps://fred:trustno1@example.org - 127.0.0.1:1234 - amqps://127.0.0.1:1234 Sending & Receiving Messages ============================ The L{Messenger} class works in conjuction with the L{Message} class. The L{Message} class is a mutable holder of message content. The L{put} method copies its L{Message} to the outgoing queue, and may send queued messages if it can do so without blocking. The L{send} method blocks until it has sent the requested number of messages, or until a timeout interrupts the attempt. >>> message = Message() >>> for i in range(3): ... message.address = "amqp://host/queue" ... message.subject = "Hello World %i" % i ... messenger.put(message) >>> messenger.send() Similarly, the L{recv} method receives messages into the incoming queue, and may block as it attempts to receive the requested number of messages, or until timeout is reached. It may receive fewer than the requested number. The L{get} method pops the eldest L{Message} off the incoming queue and copies it into the L{Message} object that you supply. It will not block. >>> message = Message() >>> messenger.recv(10): >>> while messenger.incoming > 0: ... messenger.get(message) ... print message.subject Hello World 0 Hello World 1 Hello World 2 The blocking flag allows you to turn off blocking behavior entirely, in which case L{send} and L{recv} will do whatever they can without blocking, and then return. You can then look at the number of incoming and outgoing messages to see how much outstanding work still remains. """ def __init__(self, name=None): """ Construct a new L{Messenger} with the given name. The name has global scope. If a NULL name is supplied, a UUID based name will be chosen. @type name: string @param name: the name of the messenger or None """ self._mng = pn_messenger(name) self._selectables = {} def __del__(self): """ Destroy the L{Messenger}. This will close all connections that are managed by the L{Messenger}. Call the L{stop} method before destroying the L{Messenger}. """ if hasattr(self, "_mng"): pn_messenger_free(self._mng) del self._mng def _check(self, err): if err < 0: if (err == PN_INPROGRESS): return exc = EXCEPTIONS.get(err, MessengerException) raise exc("[%s]: %s" % (err, pn_error_text(pn_messenger_error(self._mng)))) else: return err @property def name(self): """ The name of the L{Messenger}. """ return pn_messenger_name(self._mng) def _get_certificate(self): return pn_messenger_get_certificate(self._mng) def _set_certificate(self, value): self._check(pn_messenger_set_certificate(self._mng, value)) certificate = property(_get_certificate, _set_certificate, doc=""" Path to a certificate file for the L{Messenger}. This certificate is used when the L{Messenger} accepts or establishes SSL/TLS connections. This property must be specified for the L{Messenger} to accept incoming SSL/TLS connections and to establish client authenticated outgoing SSL/TLS connection. Non client authenticated outgoing SSL/TLS connections do not require this property. """) def _get_private_key(self): return pn_messenger_get_private_key(self._mng) def _set_private_key(self, value): self._check(pn_messenger_set_private_key(self._mng, value)) private_key = property(_get_private_key, _set_private_key, doc=""" Path to a private key file for the L{Messenger's} certificate. This property must be specified for the L{Messenger} to accept incoming SSL/TLS connections and to establish client authenticated outgoing SSL/TLS connection. Non client authenticated SSL/TLS connections do not require this property. """) def _get_password(self): return pn_messenger_get_password(self._mng) def _set_password(self, value): self._check(pn_messenger_set_password(self._mng, value)) password = property(_get_password, _set_password, doc=""" This property contains the password for the L{Messenger.private_key} file, or None if the file is not encrypted. """) def _get_trusted_certificates(self): return pn_messenger_get_trusted_certificates(self._mng) def _set_trusted_certificates(self, value): self._check(pn_messenger_set_trusted_certificates(self._mng, value)) trusted_certificates = property(_get_trusted_certificates, _set_trusted_certificates, doc=""" A path to a database of trusted certificates for use in verifying the peer on an SSL/TLS connection. If this property is None, then the peer will not be verified. """) def _get_timeout(self): t = pn_messenger_get_timeout(self._mng) if t == -1: return None else: return millis2secs(t) def _set_timeout(self, value): if value is None: t = -1 else: t = secs2millis(value) self._check(pn_messenger_set_timeout(self._mng, t)) timeout = property(_get_timeout, _set_timeout, doc=""" The timeout property contains the default timeout for blocking operations performed by the L{Messenger}. """) def _is_blocking(self): return pn_messenger_is_blocking(self._mng) def _set_blocking(self, b): self._check(pn_messenger_set_blocking(self._mng, b)) blocking = property(_is_blocking, _set_blocking, doc=""" Enable or disable blocking behavior during L{Message} sending and receiving. This affects every blocking call, with the exception of L{work}. Currently, the affected calls are L{send}, L{recv}, and L{stop}. """) def _is_passive(self): return pn_messenger_is_passive(self._mng) def _set_passive(self, b): self._check(pn_messenger_set_passive(self._mng, b)) passive = property(_is_passive, _set_passive, doc=""" When passive is set to true, Messenger will not attempt to perform I/O internally. In this mode it is necessary to use the selectables API to drive any I/O needed to perform requested actions. In this mode Messenger will never block. """) def _get_incoming_window(self): return pn_messenger_get_incoming_window(self._mng) def _set_incoming_window(self, window): self._check(pn_messenger_set_incoming_window(self._mng, window)) incoming_window = property(_get_incoming_window, _set_incoming_window, doc=""" The incoming tracking window for the messenger. The messenger will track the remote status of this many incoming deliveries after they have been accepted or rejected. Defaults to zero. L{Messages} enter this window only when you take them into your application using L{get}. If your incoming window size is I{n}, and you get I{n}+1 L{messages} without explicitly accepting or rejecting the oldest message, then the message that passes beyond the edge of the incoming window will be assigned the default disposition of its link. """) def _get_outgoing_window(self): return pn_messenger_get_outgoing_window(self._mng) def _set_outgoing_window(self, window): self._check(pn_messenger_set_outgoing_window(self._mng, window)) outgoing_window = property(_get_outgoing_window, _set_outgoing_window, doc=""" The outgoing tracking window for the messenger. The messenger will track the remote status of this many outgoing deliveries after calling send. Defaults to zero. A L{Message} enters this window when you call the put() method with the message. If your outgoing window size is I{n}, and you call L{put} I{n}+1 times, status information will no longer be available for the first message. """) def start(self): """ Currently a no-op placeholder. For future compatibility, do not L{send} or L{recv} messages before starting the L{Messenger}. """ self._check(pn_messenger_start(self._mng)) def stop(self): """ Transitions the L{Messenger} to an inactive state. An inactive L{Messenger} will not send or receive messages from its internal queues. A L{Messenger} should be stopped before being discarded to ensure a clean shutdown handshake occurs on any internally managed connections. """ self._check(pn_messenger_stop(self._mng)) @property def stopped(self): """ Returns true iff a L{Messenger} is in the stopped state. This function does not block. """ return pn_messenger_stopped(self._mng) def subscribe(self, source): """ Subscribes the L{Messenger} to messages originating from the specified source. The source is an address as specified in the L{Messenger} introduction with the following addition. If the domain portion of the address begins with the '~' character, the L{Messenger} will interpret the domain as host/port, bind to it, and listen for incoming messages. For example "~0.0.0.0", "amqp://~0.0.0.0", and "amqps://~0.0.0.0" will all bind to any local interface and listen for incoming messages with the last variant only permitting incoming SSL connections. @type source: string @param source: the source of messages to subscribe to """ sub_impl = pn_messenger_subscribe(self._mng, source) if not sub_impl: self._check(pn_error_code(pn_messenger_error(self._mng))) raise MessengerException("Cannot subscribe to %s"%source) return Subscription(sub_impl) def put(self, message): """ Places the content contained in the message onto the outgoing queue of the L{Messenger}. This method will never block, however it will send any unblocked L{Messages} in the outgoing queue immediately and leave any blocked L{Messages} remaining in the outgoing queue. The L{send} call may be used to block until the outgoing queue is empty. The L{outgoing} property may be used to check the depth of the outgoing queue. When the content in a given L{Message} object is copied to the outgoing message queue, you may then modify or discard the L{Message} object without having any impact on the content in the outgoing queue. This method returns an outgoing tracker for the L{Message}. The tracker can be used to determine the delivery status of the L{Message}. @type message: Message @param message: the message to place in the outgoing queue @return: a tracker """ message._pre_encode() self._check(pn_messenger_put(self._mng, message._msg)) return pn_messenger_outgoing_tracker(self._mng) def status(self, tracker): """ Gets the last known remote state of the delivery associated with the given tracker. @type tracker: tracker @param tracker: the tracker whose status is to be retrieved @return: one of None, PENDING, REJECTED, or ACCEPTED """ disp = pn_messenger_status(self._mng, tracker); return STATUSES.get(disp, disp) def buffered(self, tracker): """ Checks if the delivery associated with the given tracker is still waiting to be sent. @type tracker: tracker @param tracker: the tracker whose status is to be retrieved @return: true if delivery is still buffered """ return pn_messenger_buffered(self._mng, tracker); def settle(self, tracker=None): """ Frees a L{Messenger} from tracking the status associated with a given tracker. If you don't supply a tracker, all outgoing L{messages} up to the most recent will be settled. """ if tracker is None: tracker = pn_messenger_outgoing_tracker(self._mng) flags = PN_CUMULATIVE else: flags = 0 self._check(pn_messenger_settle(self._mng, tracker, flags)) def send(self, n=-1): """ This call will block until the indicated number of L{messages} have been sent, or until the operation times out. If n is -1 this call will block until all outgoing L{messages} have been sent. If n is 0 then this call will send whatever it can without blocking. """ self._check(pn_messenger_send(self._mng, n)) def recv(self, n=None): """ Receives up to I{n} L{messages} into the incoming queue. If no value for I{n} is supplied, this call will receive as many L{messages} as it can buffer internally. If the L{Messenger} is in blocking mode, this call will block until at least one L{Message} is available in the incoming queue. """ if n is None: n = -1 self._check(pn_messenger_recv(self._mng, n)) def work(self, timeout=None): """ Sends or receives any outstanding L{messages} queued for a L{Messenger}. This will block for the indicated timeout. This method may also do I/O work other than sending and receiving L{messages}. For example, closing connections after messenger.L{stop}() has been called. """ if timeout is None: t = -1 else: t = secs2millis(timeout) err = pn_messenger_work(self._mng, t) if (err == PN_TIMEOUT): return False else: self._check(err) return True @property def receiving(self): return pn_messenger_receiving(self._mng) def interrupt(self): """ The L{Messenger} interface is single-threaded. This is the only L{Messenger} function intended to be called from outside of the L{Messenger} thread. Call this from a non-messenger thread to interrupt a L{Messenger} that is blocking. This will cause any in-progress blocking call to throw the L{Interrupt} exception. If there is no currently blocking call, then the next blocking call will be affected, even if it is within the same thread that interrupt was called from. """ self._check(pn_messenger_interrupt(self._mng)) def get(self, message=None): """ Moves the message from the head of the incoming message queue into the supplied message object. Any content in the message will be overwritten. A tracker for the incoming L{Message} is returned. The tracker can later be used to communicate your acceptance or rejection of the L{Message}. If None is passed in for the L{Message} object, the L{Message} popped from the head of the queue is discarded. @type message: Message @param message: the destination message object @return: a tracker """ if message is None: impl = None else: impl = message._msg self._check(pn_messenger_get(self._mng, impl)) if message is not None: message._post_decode() return pn_messenger_incoming_tracker(self._mng) def accept(self, tracker=None): """ Signal the sender that you have acted on the L{Message} pointed to by the tracker. If no tracker is supplied, then all messages that have been returned by the L{get} method are accepted, except those that have already been auto-settled by passing beyond your incoming window size. @type tracker: tracker @param tracker: a tracker as returned by get """ if tracker is None: tracker = pn_messenger_incoming_tracker(self._mng) flags = PN_CUMULATIVE else: flags = 0 self._check(pn_messenger_accept(self._mng, tracker, flags)) def reject(self, tracker=None): """ Rejects the L{Message} indicated by the tracker. If no tracker is supplied, all messages that have been returned by the L{get} method are rejected, except those that have already been auto-settled by passing beyond your outgoing window size. @type tracker: tracker @param tracker: a tracker as returned by get """ if tracker is None: tracker = pn_messenger_incoming_tracker(self._mng) flags = PN_CUMULATIVE else: flags = 0 self._check(pn_messenger_reject(self._mng, tracker, flags)) @property def outgoing(self): """ The outgoing queue depth. """ return pn_messenger_outgoing(self._mng) @property def incoming(self): """ The incoming queue depth. """ return pn_messenger_incoming(self._mng) def route(self, pattern, address): """ Adds a routing rule to a L{Messenger's} internal routing table. The route procedure may be used to influence how a L{Messenger} will internally treat a given address or class of addresses. Every call to the route procedure will result in L{Messenger} appending a routing rule to its internal routing table. Whenever a L{Message} is presented to a L{Messenger} for delivery, it will match the address of this message against the set of routing rules in order. The first rule to match will be triggered, and instead of routing based on the address presented in the message, the L{Messenger} will route based on the address supplied in the rule. The pattern matching syntax supports two types of matches, a '%' will match any character except a '/', and a '*' will match any character including a '/'. A routing address is specified as a normal AMQP address, however it may additionally use substitution variables from the pattern match that triggered the rule. Any message sent to "foo" will be routed to "amqp://foo.com": >>> messenger.route("foo", "amqp://foo.com"); Any message sent to "foobar" will be routed to "amqp://foo.com/bar": >>> messenger.route("foobar", "amqp://foo.com/bar"); Any message sent to bar/ will be routed to the corresponding path within the amqp://bar.com domain: >>> messenger.route("bar/*", "amqp://bar.com/$1"); Route all L{messages} over TLS: >>> messenger.route("amqp:*", "amqps:$1") Supply credentials for foo.com: >>> messenger.route("amqp://foo.com/*", "amqp://user:password@foo.com/$1"); Supply credentials for all domains: >>> messenger.route("amqp://*", "amqp://user:password@$1"); Route all addresses through a single proxy while preserving the original destination: >>> messenger.route("amqp://%/*", "amqp://user:password@proxy/$1/$2"); Route any address through a single broker: >>> messenger.route("*", "amqp://user:password@broker/$1"); """ self._check(pn_messenger_route(self._mng, pattern, address)) def rewrite(self, pattern, address): """ Similar to route(), except that the destination of the L{Message} is determined before the message address is rewritten. The outgoing address is only rewritten after routing has been finalized. If a message has an outgoing address of "amqp://0.0.0.0:5678", and a rewriting rule that changes its outgoing address to "foo", it will still arrive at the peer that is listening on "amqp://0.0.0.0:5678", but when it arrives there, the receiver will see its outgoing address as "foo". The default rewrite rule removes username and password from addresses before they are transmitted. """ self._check(pn_messenger_rewrite(self._mng, pattern, address)) def selectable(self): impl = pn_messenger_selectable(self._mng) if impl: fd = pn_selectable_fd(impl) sel = self._selectables.get(fd, None) if sel is None: sel = Selectable(self, impl) self._selectables[fd] = sel return sel else: return None @property def deadline(self): tstamp = pn_messenger_deadline(self._mng) if tstamp: return millis2secs(tstamp) else: return 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 """ DATA = PN_DATA TEXT = PN_TEXT AMQP = PN_AMQP JSON = PN_JSON DEFAULT_PRIORITY = PN_DEFAULT_PRIORITY def __init__(self, **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 = None for k,v in kwargs.iteritems(): 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 _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: 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 pn_message_get_address(self._msg) def _set_address(self, value): self._check(pn_message_set_address(self._msg, value)) address = property(_get_address, _set_address, doc=""" The address of the message. """) def _get_subject(self): return pn_message_get_subject(self._msg) def _set_subject(self, value): self._check(pn_message_set_subject(self._msg, value)) subject = property(_get_subject, _set_subject, doc=""" The subject of the message. """) def _get_reply_to(self): return pn_message_get_reply_to(self._msg) def _set_reply_to(self, value): self._check(pn_message_set_reply_to(self._msg, 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 pn_message_get_content_type(self._msg) def _set_content_type(self, value): self._check(pn_message_set_content_type(self._msg, value)) content_type = property(_get_content_type, _set_content_type, doc=""" The content-type of the message. """) def _get_content_encoding(self): return pn_message_get_content_encoding(self._msg) def _set_content_encoding(self, value): self._check(pn_message_set_content_encoding(self._msg, 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 pn_message_get_group_id(self._msg) def _set_group_id(self, value): self._check(pn_message_set_group_id(self._msg, 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 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, value)) reply_to_group_id = property(_get_reply_to_group_id, _set_reply_to_group_id, doc=""" The group-id for any replies. """) # XXX def _get_format(self): return pn_message_get_format(self._msg) def _set_format(self, value): self._check(pn_message_set_format(self._msg, value)) format = property(_get_format, _set_format, doc=""" The format of the message. """) 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, len(data))) self._post_decode() def load(self, data): self._check(pn_message_load(self._msg, data)) def save(self): sz = 16 while True: err, data = pn_message_save(self._msg, sz) if err == PN_OVERFLOW: sz *= 2 continue else: self._check(err) return data 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 class Subscription(object): def __init__(self, impl): self._impl = impl @property def address(self): return pn_subscription_address(self._impl) class Selectable(object): def __init__(self, messenger, impl): self.messenger = messenger self._impl = impl def fileno(self): if not self._impl: raise ValueError("selectable freed") return pn_selectable_fd(self._impl) @property def capacity(self): if not self._impl: raise ValueError("selectable freed") return pn_selectable_capacity(self._impl) @property def pending(self): if not self._impl: raise ValueError("selectable freed") return pn_selectable_pending(self._impl) @property def deadline(self): if not self._impl: raise ValueError("selectable freed") tstamp = pn_selectable_deadline(self._impl) if tstamp: return millis2secs(tstamp) else: return None def readable(self): if not self._impl: raise ValueError("selectable freed") pn_selectable_readable(self._impl) def writable(self): if not self._impl: raise ValueError("selectable freed") pn_selectable_writable(self._impl) def expired(self): if not self._impl: raise ValueError("selectable freed") pn_selectable_expired(self._impl) def _is_registered(self): if not self._impl: raise ValueError("selectable freed") return pn_selectable_is_registered(self._impl) def _set_registered(self, registered): if not self._impl: raise ValueError("selectable freed") 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): if not self._impl: return True return pn_selectable_is_terminal(self._impl) def free(self): if self._impl: del self.messenger._selectables[self.fileno()] pn_selectable_free(self._impl) self._impl = None def __del__(self): self.free() 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 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 __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 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_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)) 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() >>> print data.get_symbol() >>> 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 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 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 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 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 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 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(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 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 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 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 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 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)) 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, dict: put_dict, list: put_sequence, tuple: put_sequence, unicode: put_string, bytes: put_binary, symbol: put_symbol, int: put_long, char: put_char, long: put_long, ulong: put_ulong, timestamp: put_timestamp, float: put_double, uuid.UUID: put_uuid, Described: put_py_described, Array: put_py_array } 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 self._release_invoked = False def _release(self): """Release the underlying C Engine resource.""" if not self._release_invoked: for c in self._children: c._release() self._free_resource() self.connection._releasing(self) self._release_invoked = True 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!" 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 class Connection(Endpoint): @staticmethod def _wrap_connection(c_conn): """Maintain only a single instance of this class for each Connection object that exists in the the C Engine. This is done by storing a (weak) reference to the python instance in the context field of the C object. """ if not c_conn: return None py_conn = pn_void2py(pn_connection_get_context(c_conn)) if py_conn: return py_conn wrapper = Connection(_conn=c_conn) return wrapper def __init__(self, _conn=None): Endpoint.__init__(self) if _conn: self._conn = _conn else: self._conn = pn_connection() pn_connection_set_context(self._conn, pn_py2void(self)) self.offered_capabilities = None self.desired_capabilities = None self.properties = None self._sessions = set() def __del__(self): if hasattr(self, "_conn") and self._conn: self._release() def free(self): self._release() @property def _children(self): return self._sessions @property def connection(self): return self def _free_resource(self): pn_connection_free(self._conn) def _released(self): self._conn = None def _releasing(self, child): coll = getattr(self, "_collector", None) if coll: coll = coll() if coll: coll._contexts.add(child) else: child._released() def _check(self, err): if err < 0: exc = EXCEPTIONS.get(err, ConnectionException) raise exc("[%s]: %s" % (err, pn_connection_error(self._conn))) else: return err def _get_cond_impl(self): return pn_connection_condition(self._conn) def _get_remote_cond_impl(self): return pn_connection_remote_condition(self._conn) def collect(self, collector): if collector is None: pn_connection_collect(self._conn, None) else: pn_connection_collect(self._conn, collector._impl) self._collector = weakref.ref(collector) def _get_container(self): return pn_connection_get_container(self._conn) def _set_container(self, name): return pn_connection_set_container(self._conn, name) container = property(_get_container, _set_container) def _get_hostname(self): return pn_connection_get_hostname(self._conn) def _set_hostname(self, name): return pn_connection_set_hostname(self._conn, name) hostname = property(_get_hostname, _set_hostname) @property def remote_container(self): return pn_connection_remote_container(self._conn) @property def remote_hostname(self): return pn_connection_remote_hostname(self._conn) @property def remote_offered_capabilities(self): return dat2obj(pn_connection_remote_offered_capabilities(self._conn)) @property def remote_desired_capabilities(self): return dat2obj(pn_connection_remote_desired_capabilities(self._conn)) @property def remote_properties(self): return dat2obj(pn_connection_remote_properties(self._conn)) def open(self): obj2dat(self.offered_capabilities, pn_connection_offered_capabilities(self._conn)) obj2dat(self.desired_capabilities, pn_connection_desired_capabilities(self._conn)) obj2dat(self.properties, pn_connection_properties(self._conn)) pn_connection_open(self._conn) def close(self): self._update_cond() pn_connection_close(self._conn) @property def state(self): return pn_connection_state(self._conn) def session(self): return Session._wrap_session(pn_session(self._conn)) def session_head(self, mask): return Session._wrap_session(pn_session_head(self._conn, mask)) def link_head(self, mask): return Link._wrap_link(pn_link_head(self._conn, mask)) @property def work_head(self): return Delivery._wrap_delivery(pn_work_head(self._conn)) @property def error(self): return pn_error_code(pn_connection_error(self._conn)) class SessionException(ProtonException): pass class Session(Endpoint): @staticmethod def _wrap_session(c_ssn): """Maintain only a single instance of this class for each Session object that exists in the C Engine. """ if c_ssn is None: return None py_ssn = pn_void2py(pn_session_get_context(c_ssn)) if py_ssn: return py_ssn wrapper = Session(c_ssn) return wrapper def __init__(self, ssn): Endpoint.__init__(self) self._ssn = ssn pn_session_set_context(self._ssn, pn_py2void(self)) self._links = set() self.connection._sessions.add(self) @property def _children(self): return self._links def _free_resource(self): pn_session_free(self._ssn) def _released(self): self._ssn = None def free(self): """Release the Session, freeing its resources. Call this when you no longer need the session. This will allow the session's resources to be reclaimed. Once called, you should no longer reference the session. """ self.connection._sessions.remove(self) self._release() def _get_cond_impl(self): return pn_session_condition(self._ssn) def _get_remote_cond_impl(self): return pn_session_remote_condition(self._ssn) def _get_incoming_capacity(self): return pn_session_get_incoming_capacity(self._ssn) def _set_incoming_capacity(self, capacity): pn_session_set_incoming_capacity(self._ssn, capacity) incoming_capacity = property(_get_incoming_capacity, _set_incoming_capacity) @property def outgoing_bytes(self): return pn_session_outgoing_bytes(self._ssn) @property def incoming_bytes(self): return pn_session_incoming_bytes(self._ssn) def open(self): pn_session_open(self._ssn) def close(self): self._update_cond() pn_session_close(self._ssn) def next(self, mask): return Session._wrap_session(pn_session_next(self._ssn, mask)) @property def state(self): return pn_session_state(self._ssn) @property def connection(self): return Connection._wrap_connection(pn_session_connection(self._ssn)) def sender(self, name): return Link._wrap_link(pn_sender(self._ssn, name)) def receiver(self, name): return Link._wrap_link(pn_receiver(self._ssn, name)) class LinkException(ProtonException): pass class Link(Endpoint): 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_link(c_link): """Maintain only a single instance of this class for each Session object that exists in the C Engine. """ if c_link is None: return None py_link = pn_void2py(pn_link_get_context(c_link)) if py_link: return py_link if pn_link_is_sender(c_link): wrapper = Sender(c_link) else: wrapper = Receiver(c_link) return wrapper def __init__(self, c_link): Endpoint.__init__(self) self._link = c_link pn_link_set_context(self._link, pn_py2void(self)) self._deliveries = set() self.session._links.add(self) @property def _children(self): return self._deliveries def _free_resource(self): pn_link_free(self._link) def _released(self): self._link = None def free(self): """Release the Link, freeing its resources""" self.session._links.remove(self) self._release() def _check(self, err): if err < 0: exc = EXCEPTIONS.get(err, LinkException) raise exc("[%s]: %s" % (err, pn_link_error(self._link))) else: return err def _get_cond_impl(self): return pn_link_condition(self._link) def _get_remote_cond_impl(self): return pn_link_remote_condition(self._link) def open(self): pn_link_open(self._link) def close(self): self._update_cond() pn_link_close(self._link) @property def state(self): return pn_link_state(self._link) @property def source(self): return Terminus(pn_link_source(self._link)) @property def target(self): return Terminus(pn_link_target(self._link)) @property def remote_source(self): return Terminus(pn_link_remote_source(self._link)) @property def remote_target(self): return Terminus(pn_link_remote_target(self._link)) @property def session(self): return Session._wrap_session(pn_link_session(self._link)) @property def connection(self): return self.session.connection def delivery(self, tag): return Delivery._wrap_delivery(pn_delivery(self._link, tag)) @property def current(self): return Delivery._wrap_delivery(pn_link_current(self._link)) def advance(self): return pn_link_advance(self._link) @property def unsettled(self): return pn_link_unsettled(self._link) @property def credit(self): return pn_link_credit(self._link) @property def available(self): return pn_link_available(self._link) @property def queued(self): return pn_link_queued(self._link) def next(self, mask): return Link._wrap_link(pn_link_next(self._link, mask)) @property def name(self): return pn_link_name(self._link) @property def is_sender(self): return pn_link_is_sender(self._link) @property def is_receiver(self): return pn_link_is_receiver(self._link) @property def remote_snd_settle_mode(self): return pn_link_remote_snd_settle_mode(self._link) @property def remote_rcv_settle_mode(self): return pn_link_remote_rcv_settle_mode(self._link) def _get_snd_settle_mode(self): return pn_link_snd_settle_mode(self._link) def _set_snd_settle_mode(self, mode): pn_link_set_snd_settle_mode(self._link, 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._link) def _set_rcv_settle_mode(self, mode): pn_link_set_rcv_settle_mode(self._link, mode) rcv_settle_mode = property(_get_rcv_settle_mode, _set_rcv_settle_mode) def drained(self): return pn_link_drained(self._link) def detach(self): return pn_link_detach(self._link) 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 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): return pn_terminus_get_address(self._impl) def _set_address(self, address): self._check(pn_terminus_set_address(self._impl, 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): 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): return Data(pn_terminus_properties(self._impl)) @property def capabilities(self): return Data(pn_terminus_capabilities(self._impl)) @property def outcomes(self): return Data(pn_terminus_outcomes(self._impl)) @property def filter(self): return Data(pn_terminus_filter(self._impl)) def copy(self, src): self._check(pn_terminus_copy(self._impl, src._impl)) class Sender(Link): def __init__(self, c_link): super(Sender, self).__init__(c_link) def offered(self, n): pn_link_offered(self._link, n) def send(self, bytes): return self._check(pn_link_send(self._link, bytes)) class Receiver(Link): def __init__(self, c_link): super(Receiver, self).__init__(c_link) def flow(self, n): pn_link_flow(self._link, n) def recv(self, limit): n, bytes = pn_link_recv(self._link, limit) if n == PN_EOS: return None else: self._check(n) return bytes def drain(self, n): pn_link_drain(self._link, n) def draining(self): return pn_link_draining(self._link) 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(object): RECEIVED = Disposition.RECEIVED ACCEPTED = Disposition.ACCEPTED REJECTED = Disposition.REJECTED RELEASED = Disposition.RELEASED MODIFIED = Disposition.MODIFIED @staticmethod def _wrap_delivery(c_dlv): """Maintain only a single instance of this class for each Delivery object that exists in the C Engine. """ if not c_dlv: return None py_dlv = pn_void2py(pn_delivery_get_context(c_dlv)) if py_dlv: return py_dlv wrapper = Delivery(c_dlv) return wrapper def __init__(self, dlv): self._dlv = dlv pn_delivery_set_context(self._dlv, pn_py2void(self)) self.local = Disposition(pn_delivery_local(self._dlv), True) self.remote = Disposition(pn_delivery_remote(self._dlv), False) self.link._deliveries.add(self) def __del__(self): self._release() def _release(self): """Release the underlying C Engine resource.""" if self._dlv: pn_delivery_set_context(self._dlv, pn_py2void(None)) pn_delivery_settle(self._dlv) self._dlv = None @property def released(self): return self._dlv is None @property def tag(self): return pn_delivery_tag(self._dlv) @property def writable(self): return pn_delivery_writable(self._dlv) @property def readable(self): return pn_delivery_readable(self._dlv) @property def updated(self): return pn_delivery_updated(self._dlv) def update(self, state): 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._dlv, state) @property def pending(self): return pn_delivery_pending(self._dlv) @property def partial(self): return pn_delivery_partial(self._dlv) @property def local_state(self): return DispositionType.get(pn_delivery_local_state(self._dlv)) @property def remote_state(self): return DispositionType.get(pn_delivery_remote_state(self._dlv)) @property def settled(self): return pn_delivery_settled(self._dlv) def settle(self): """Release the delivery""" self.link._deliveries.remove(self) self._release() @property def work_next(self): return Delivery._wrap_delivery(pn_work_next(self._dlv)) @property def link(self): return Link._wrap_link(pn_delivery_link(self._dlv)) class TransportException(ProtonException): pass class Transport(object): 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_transport(c_trans): if not c_trans: return None wrapper = Transport(_trans=c_trans) return wrapper def __init__(self, mode=None, _trans=None): if not mode and not _trans: self._trans = pn_transport() elif not mode: self._shared_trans = True self._trans = _trans elif mode==Transport.CLIENT: self._trans = pn_transport() elif mode==Transport.SERVER: self._trans = pn_transport() pn_transport_set_server(self._trans) else: raise TransportException("Cannot initialise Transport from mode: %s" % str(mode)) self._sasl = None self._ssl = None def __del__(self): if hasattr(self, "_trans"): if not hasattr(self, "_shared_trans"): pn_transport_free(self._trans) if hasattr(self, "_sasl") and self._sasl: # pn_transport_free deallocs the C sasl associated with the # transport, so erase the reference if a SASL object was used. self._sasl._sasl = None self._sasl = None if hasattr(self, "_ssl") and self._ssl: # ditto the owned c SSL object self._ssl._ssl = None self._ssl = None del self._trans def _check(self, err): if err < 0: exc = EXCEPTIONS.get(err, TransportException) raise exc("[%s]: %s" % (err, pn_error_text(pn_transport_error(self._trans)))) else: return err def bind(self, connection): """Assign a connection to the transport""" self._check(pn_transport_bind(self._trans, connection._conn)) # keep python connection from being garbage collected: self._connection = connection def unbind(self): """Release the connection""" self._check(pn_transport_unbind(self._trans)) self._connection = None def trace(self, n): pn_transport_trace(self._trans, n) def tick(self, now): """Process any timed events (like heartbeat generation). now = seconds since epoch (float). """ return millis2secs(pn_transport_tick(self._trans, secs2millis(now))) def capacity(self): c = pn_transport_capacity(self._trans) if c >= PN_EOS: return c else: return self._check(c) def push(self, bytes): n = self._check(pn_transport_push(self._trans, bytes)) if n != len(bytes): raise OverflowError("unable to process all bytes") def close_tail(self): self._check(pn_transport_close_tail(self._trans)) def pending(self): p = pn_transport_pending(self._trans) if p >= PN_EOS: return p else: return self._check(p) def peek(self, size): cd, out = pn_transport_peek(self._trans, size) if cd == PN_EOS: return None else: self._check(cd) return out def pop(self, size): pn_transport_pop(self._trans, size) def close_head(self): self._check(pn_transport_close_head(self._trans)) @property def closed(self): return pn_transport_closed(self._trans) # AMQP 1.0 max-frame-size def _get_max_frame_size(self): return pn_transport_get_max_frame(self._trans) def _set_max_frame_size(self, value): pn_transport_set_max_frame(self._trans, 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._trans) def _get_channel_max(self): return pn_transport_get_channel_max(self._trans) def _set_channel_max(self, value): pn_transport_set_channel_max(self._trans, value) 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._trans) # AMQP 1.0 idle-time-out def _get_idle_timeout(self): return millis2secs(pn_transport_get_idle_timeout(self._trans)) def _set_idle_timeout(self, sec): pn_transport_set_idle_timeout(self._trans, 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._trans)) @property def frames_output(self): return pn_transport_get_frames_output(self._trans) @property def frames_input(self): return pn_transport_get_frames_input(self._trans) def sasl(self): # SASL factory (singleton for this transport) if not self._sasl: self._sasl = SASL(self) return self._sasl 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._trans)) @property def connection(self): return Connection._wrap_connection(pn_transport_connection(self._trans)) class SASLException(TransportException): pass class SASL(object): OK = PN_SASL_OK AUTH = PN_SASL_AUTH SKIPPED = PN_SASL_SKIPPED def __new__(cls, transport): """Enforce a singleton SASL object per Transport""" if not transport._sasl: obj = super(SASL, cls).__new__(cls) obj._sasl = pn_sasl(transport._trans) transport._sasl = obj return transport._sasl def _check(self, err): if err < 0: exc = EXCEPTIONS.get(err, SASLException) raise exc("[%s]" % (err)) else: return err def mechanisms(self, mechs): pn_sasl_mechanisms(self._sasl, mechs) # @deprecated def client(self): pn_sasl_client(self._sasl) # @deprecated def server(self): pn_sasl_server(self._sasl) def allow_skip(self, allow): pn_sasl_allow_skip(self._sasl, allow) def plain(self, user, password): pn_sasl_plain(self._sasl, user, password) def send(self, data): self._check(pn_sasl_send(self._sasl, data, len(data))) def recv(self): sz = 16 while True: n, data = pn_sasl_recv(self._sasl, sz) if n == PN_OVERFLOW: sz *= 2 continue elif n == PN_EOS: return None else: self._check(n) return data @property def outcome(self): outcome = pn_sasl_outcome(self._sasl) if outcome == PN_SASL_NONE: return None else: return outcome def done(self, outcome): pn_sasl_done(self._sasl, outcome) STATE_IDLE = PN_SASL_IDLE STATE_STEP = PN_SASL_STEP STATE_PASS = PN_SASL_PASS STATE_FAIL = PN_SASL_FAIL @property def state(self): return pn_sasl_state(self._sasl) 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) ) 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._trans ) if obj._ssl is None: raise SSLUnavailable() 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 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, hostname )) def _get_peer_hostname(self): err, name = pn_ssl_get_peer_hostname( self._ssl, 1024 ) self._check(err) return 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_connection(pn_cast_pn_connection(x)), "pn_session": lambda x: Session._wrap_session(pn_cast_pn_session(x)), "pn_link": lambda x: Link._wrap_link(pn_cast_pn_link(x)), "pn_delivery": lambda x: Delivery._wrap_delivery(pn_cast_pn_delivery(x)), "pn_transport": lambda x: Transport._wrap_transport(pn_cast_pn_transport(x)) } class Collector: def __init__(self): self._impl = pn_collector() self._contexts = set() def put(self, obj, etype): pn_collector_put(self._impl, PN_PYREF, pn_py2void(obj), etype.number) def peek(self): event = pn_collector_peek(self._impl) if event is None: return None clazz = pn_class_name(pn_event_class(event)) context = wrappers[clazz](pn_event_context(event)) return Event(clazz, context, EventType.TYPES[pn_event_type(event)]) def pop(self): ev = self.peek() if ev is not None: ev._popped(self) pn_collector_pop(self._impl) def __del__(self): pn_collector_free(self._impl) class EventType(object): TYPES = {} def __init__(self, number, method): self.number = number self.name = pn_event_type_name(self.number) self.method = method self.TYPES[number] = self 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 Event(object): CONNECTION_INIT = EventType(PN_CONNECTION_INIT, "on_connection_init") CONNECTION_BOUND = EventType(PN_CONNECTION_BOUND, "on_connection_bound") CONNECTION_UNBOUND = EventType(PN_CONNECTION_UNBOUND, "on_connection_unbound") CONNECTION_LOCAL_OPEN = EventType(PN_CONNECTION_LOCAL_OPEN, "on_connection_local_open") CONNECTION_LOCAL_CLOSE = EventType(PN_CONNECTION_LOCAL_CLOSE, "on_connection_local_close") CONNECTION_REMOTE_OPEN = EventType(PN_CONNECTION_REMOTE_OPEN, "on_connection_remote_open") CONNECTION_REMOTE_CLOSE = EventType(PN_CONNECTION_REMOTE_CLOSE, "on_connection_remote_close") CONNECTION_FINAL = EventType(PN_CONNECTION_FINAL, "on_connection_final") SESSION_INIT = EventType(PN_SESSION_INIT, "on_session_init") SESSION_LOCAL_OPEN = EventType(PN_SESSION_LOCAL_OPEN, "on_session_local_open") SESSION_LOCAL_CLOSE = EventType(PN_SESSION_LOCAL_CLOSE, "on_session_local_close") SESSION_REMOTE_OPEN = EventType(PN_SESSION_REMOTE_OPEN, "on_session_remote_open") SESSION_REMOTE_CLOSE = EventType(PN_SESSION_REMOTE_CLOSE, "on_session_remote_close") SESSION_FINAL = EventType(PN_SESSION_FINAL, "on_session_final") LINK_INIT = EventType(PN_LINK_INIT, "on_link_init") LINK_LOCAL_OPEN = EventType(PN_LINK_LOCAL_OPEN, "on_link_local_open") LINK_LOCAL_CLOSE = EventType(PN_LINK_LOCAL_CLOSE, "on_link_local_close") LINK_LOCAL_DETACH = EventType(PN_LINK_LOCAL_DETACH, "on_link_local_detach") LINK_REMOTE_OPEN = EventType(PN_LINK_REMOTE_OPEN, "on_link_remote_open") LINK_REMOTE_CLOSE = EventType(PN_LINK_REMOTE_CLOSE, "on_link_remote_close") LINK_REMOTE_DETACH = EventType(PN_LINK_REMOTE_DETACH, "on_link_remote_detach") LINK_FLOW = EventType(PN_LINK_FLOW, "on_link_flow") LINK_FINAL = EventType(PN_LINK_FINAL, "on_link_final") DELIVERY = EventType(PN_DELIVERY, "on_delivery") TRANSPORT = EventType(PN_TRANSPORT, "on_transport") TRANSPORT_ERROR = EventType(PN_TRANSPORT_ERROR, "on_transport_error") TRANSPORT_HEAD_CLOSED = EventType(PN_TRANSPORT_HEAD_CLOSED, "on_transport_head_closed") TRANSPORT_TAIL_CLOSED = EventType(PN_TRANSPORT_TAIL_CLOSED, "on_transport_tail_closed") TRANSPORT_CLOSED = EventType(PN_TRANSPORT_CLOSED, "on_transport_closed") def __init__(self, clazz, context, type): self.clazz = clazz self.context = context self.type = type def _popped(self, collector): if self.type in (Event.LINK_FINAL, Event.SESSION_FINAL, Event.CONNECTION_FINAL): collector._contexts.remove(self.context) self.context._released() def dispatch(self, handler): return dispatch(handler, self.type.method, self) @property def connection(self): if self.clazz == "pn_connection": return self.context elif self.clazz == "pn_session": return self.context.connection elif self.clazz == "pn_link": return self.context.connection elif self.clazz == "pn_delivery" and not self.context.released: return self.context.link.connection elif hasattr(self.context, 'connection'): return self.context.connection else: return None @property def session(self): if self.clazz == "pn_session": return self.context elif self.clazz == "pn_link": return self.context.session elif self.clazz == "pn_delivery" and not self.context.released: return self.context.link.session elif hasattr(self.context, 'session'): return self.context.session else: return None @property def link(self): if self.clazz == "pn_link": return self.context elif self.clazz == "pn_delivery" and not self.context.released: return self.context.link elif hasattr(self.context, 'link'): return self.context.link else: return None @property def sender(self): l = self.link if l and l.is_sender: return l else: return None @property def receiver(self): l = self.link if l and l.is_receiver: return l else: return None @property def delivery(self): if self.clazz == "pn_delivery": return self.context elif hasattr(self.context, 'delivery'): return self.context.delivery else: return None def __getattr__(self, name): if hasattr(self.context, name): return getattr(self.context, name) else: raise AttributeError def __repr__(self): return "%s(%s)" % (self.type, self.context) class Handler(object): def on_unhandled(self, method, args): pass ### # Driver ### class DriverException(ProtonException): """ The DriverException class is the root of the driver exception hierarchy. """ pass class Connector(object): @staticmethod def _wrap_connector(c_cxtr, py_driver=None): """Maintain only a single instance of this class for each Connector object that exists in the C Driver. """ if not c_cxtr: return None py_cxtr = pn_void2py(pn_connector_context(c_cxtr)) if py_cxtr: return py_cxtr wrapper = Connector(_cxtr=c_cxtr, _py_driver=py_driver) return wrapper def __init__(self, _cxtr, _py_driver): self._cxtr = _cxtr assert(_py_driver) self._driver = weakref.ref(_py_driver) pn_connector_set_context(self._cxtr, pn_py2void(self)) self._connection = None self._driver()._connectors.add(self) def _release(self): """Release the underlying C Engine resource.""" if self._cxtr: pn_connector_set_context(self._cxtr, pn_py2void(None)) pn_connector_free(self._cxtr) self._cxtr = None def free(self): """Release the Connector, freeing its resources. Call this when you no longer need the Connector. This will allow the connector's resources to be reclaimed. Once called, you should no longer reference this connector. """ self.connection = None d = self._driver() if d: d._connectors.remove(self) self._release() def next(self): return Connector._wrap_connector(pn_connector_next(self._cxtr)) def process(self): pn_connector_process(self._cxtr) def listener(self): return Listener._wrap_listener(pn_connector_listener(self._cxtr)) def sasl(self): ## seems easier just to grab the SASL associated with the transport: trans = self.transport if trans: return SASL(self.transport) return None @property def transport(self): trans = pn_connector_transport(self._cxtr) return Transport._wrap_transport(trans) def close(self): return pn_connector_close(self._cxtr) @property def closed(self): return pn_connector_closed(self._cxtr) def _get_connection(self): return self._connection def _set_connection(self, conn): if conn: pn_connector_set_connection(self._cxtr, conn._conn) else: pn_connector_set_connection(self._cxtr, None) self._connection = conn connection = property(_get_connection, _set_connection, doc=""" Associate a Connection with this Connector. """) class Listener(object): @staticmethod def _wrap_listener(c_lsnr, py_driver=None): """Maintain only a single instance of this class for each Listener object that exists in the C Driver. """ if not c_lsnr: return None py_lsnr = pn_void2py(pn_listener_context(c_lsnr)) if py_lsnr: return py_lsnr wrapper = Listener(_lsnr=c_lsnr, _py_driver=py_driver) return wrapper def __init__(self, _lsnr, _py_driver): self._lsnr = _lsnr assert(_py_driver) self._driver = weakref.ref(_py_driver) pn_listener_set_context(self._lsnr, pn_py2void(self)) self._driver()._listeners.add(self) def _release(self): """Release the underlying C Engine resource.""" if self._lsnr: pn_listener_set_context(self._lsnr, pn_py2void(None)); pn_listener_free(self._lsnr) self._lsnr = None def free(self): """Release the Listener, freeing its resources""" d = self._driver() if d: d._listeners.remove(self) self._release() def next(self): return Listener._wrap_listener(pn_listener_next(self._lsnr)) def accept(self): d = self._driver() if d: cxtr = pn_listener_accept(self._lsnr) c = Connector._wrap_connector(cxtr, d) return c return None def close(self): pn_listener_close(self._lsnr) class Driver(object): def __init__(self): self._driver = pn_driver() self._listeners = set() self._connectors = set() def __del__(self): # freeing the driver will release all child objects in the C Engine, so # clean up their references in the corresponding Python objects for c in self._connectors: c._release() for l in self._listeners: l._release() if hasattr(self, "_driver") and self._driver: pn_driver_free(self._driver) del self._driver def wait(self, timeout_sec): if timeout_sec is None or timeout_sec < 0.0: t = -1 else: t = secs2millis(timeout_sec) return pn_driver_wait(self._driver, t) def wakeup(self): return pn_driver_wakeup(self._driver) def listener(self, host, port): """Construct a listener""" return Listener._wrap_listener(pn_listener(self._driver, host, port, None), self) def pending_listener(self): return Listener._wrap_listener(pn_driver_listener(self._driver)) def head_listener(self): return Listener._wrap_listener(pn_listener_head(self._driver)) def connector(self, host, port): return Connector._wrap_connector(pn_connector(self._driver, host, port, None), self) def head_connector(self): return Connector._wrap_connector(pn_connector_head(self._driver)) def pending_connector(self): return Connector._wrap_connector(pn_driver_connector(self._driver)) class Url(object): """ Simple URL parser/constructor, handles URLs of the form: ://:@:/ All components can be None if not specifeid 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. @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, **kwargs): """ @param url: URL string to parse. @param kwargs: scheme, user, password, host, port, path. If specified, replaces corresponding part in url string. """ if url: self._url = pn_url_parse(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]) 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(%r)" % str(self) def __del__(self): pn_url_free(self._url); self._url = None 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", "AUTOMATIC", "PENDING", "MANUAL", "REJECTED", "RELEASED", "SETTLED", "UNDESCRIBED", "Array", "Collector", "Condition", "Connection", "Connector", "Data", "Delivery", "Disposition", "Described", "Driver", "DriverException", "Endpoint", "Event", "Handler", "Link", "Listener", "Message", "MessageException", "Messenger", "MessengerException", "ProtonException", "VERSION_MAJOR", "VERSION_MINOR", "Receiver", "SASL", "Sender", "Session", "SSL", "SSLDomain", "SSLSessionDetails", "SSLUnavailable", "SSLException", "Terminus", "Timeout", "Interrupt", "Transport", "TransportException", "Url", "char", "dispatch", "symbol", "timestamp", "ulong" ]