def __init__(self, handler, ack=True, errorDestination=None, onMessageFailed=None): if not callable(handler): raise ValueError('Handler is not callable: %s' % handler) self._handler = handler self._ack = ack self._errorDestination = errorDestination self._onMessageFailed = onMessageFailed or sendToErrorDestination self._headers = None self._messages = InFlightOperations('Handler for message') self.log = logging.getLogger(LOG_CATEGORY)
class ReceiptListener(Listener): """:param timeout: When a STOMP frame was sent to the broker and a **RECEIPT** frame was requested, this is the time (in seconds) to wait for **RECEIPT** frames to arrive. If :obj:`None`, we will wait indefinitely. **Example**: >>> client.add(ReceiptListener(1.0)) """ def __init__(self, timeout=None): self._timeout = timeout self._receipts = InFlightOperations('Waiting for receipt') self.log = logging.getLogger(LOG_CATEGORY) def onConnectionLost(self, connection, reason): # @UnusedVariable for waiting in list(self._receipts.values()): if waiting.called: continue waiting.errback(StompCancelledError('Receipt did not arrive (connection lost)')) @defer.inlineCallbacks def onSend(self, connection, frame): # @UnusedVariable if not frame: defer.returnValue(None) receipt = frame.headers.get(StompSpec.RECEIPT_HEADER) if receipt is None: defer.returnValue(None) with self._receipts(receipt, self.log) as receiptArrived: yield receiptArrived.wait(self._timeout, StompCancelledError('Receipt did not arrive on time: %s [timeout=%s]' % (receipt, self._timeout))) def onReceipt(self, connection, frame, receipt): # @UnusedVariable self._receipts[receipt].callback(None)
def test_timeout(self): op = InFlightOperations('test') with op(None) as w: try: yield w.wait(timeout=0, fail=RuntimeError('hi')) except RuntimeError as e: self.assertEquals(str(e), 'hi') else: raise self.assertEquals(list(op), [None]) self.assertEquals(list(op), [])
def test_dict_interface(self): op = InFlightOperations('test') self.assertEquals(list(op), []) self.assertRaises(KeyError, op.__getitem__, 1) self.assertRaises(KeyError, lambda: op[1]) self.assertRaises(KeyError, op.pop, 1) self.assertIdentical(op.get(1), None) self.assertIdentical(op.get(1, 2), 2) op[1] = w = defer.Deferred() self.assertEquals(list(op), [1]) self.assertIdentical(op[1], w) self.assertIdentical(op.get(1), w) self.assertRaises(KeyError, op.__setitem__, 1, defer.Deferred()) self.assertIdentical(op.pop(1), w) self.assertRaises(KeyError, op.pop, 1) op[1] = w self.assertEquals(op.popitem(), (1, w)) self.assertEquals(list(op), []) self.assertIdentical(op.setdefault(1, w), w) self.assertIdentical(op.setdefault(1, w), w)
def test_dict_interface(self): op = InFlightOperations("test") self.assertEquals(list(op), []) self.assertRaises(KeyError, op.__getitem__, 1) self.assertRaises(KeyError, lambda: op[1]) self.assertRaises(KeyError, op.pop, 1) self.assertIdentical(op.get(1), None) self.assertIdentical(op.get(1, 2), 2) op[1] = w = defer.Deferred() self.assertEquals(list(op), [1]) self.assertIdentical(op[1], w) self.assertIdentical(op.get(1), w) self.assertRaises(KeyError, op.__setitem__, 1, defer.Deferred()) self.assertIdentical(op.pop(1), w) self.assertRaises(KeyError, op.pop, 1) op[1] = w self.assertEquals(op.popitem(), (1, w)) self.assertEquals(list(op), []) self.assertIdentical(op.setdefault(1, w), w) self.assertIdentical(op.setdefault(1, w), w)
def test_context_single(self): op = InFlightOperations("test") with op(1) as w: self.assertEquals(list(op), [1]) self.assertIsInstance(w, defer.Deferred) self.assertIdentical(w, op[1]) self.assertIdentical(op.get(1), op[1]) self.assertEquals(list(op), []) with op(key=2, log=logging.getLogger(LOG_CATEGORY)): self.assertEquals(list(op), [2]) self.assertIsInstance(op.get(2), defer.Deferred) self.assertIdentical(op.get(2), op[2]) self.assertEquals(list(op), []) try: with op(None, logging.getLogger(LOG_CATEGORY)) as w: reactor.callLater(0, w.cancel) # @UndefinedVariable yield w.wait(timeout=None, fail=None) except CancelledError: pass else: raise self.assertEquals(list(op), []) try: with op(None, logging.getLogger(LOG_CATEGORY)) as w: reactor.callLater(0, w.errback, StompCancelledError("4711")) # @UndefinedVariable yield w.wait() except StompCancelledError as e: self.assertEquals(str(e), "4711") else: raise self.assertEquals(list(op), []) with op(None, logging.getLogger(LOG_CATEGORY)) as w: reactor.callLater(0, w.callback, 4711) # @UndefinedVariable result = yield w.wait() self.assertEquals(result, 4711) self.assertEquals(list(op), []) try: with op(None) as w: raise RuntimeError("hi") except RuntimeError: pass self.assertEquals(list(op), []) try: yield w except RuntimeError as e: self.assertEquals(str(e), "hi") else: raise try: with op(None) as w: d = w.wait() raise RuntimeError("hi") except RuntimeError: pass self.assertEquals(list(op), []) try: yield d except RuntimeError as e: self.assertEquals(str(e), "hi") else: pass
class SubscriptionListener(Listener): """Corresponds to a STOMP subscription. :param handler: A callable :obj:`f(client, frame)` which accepts a :class:`~.async.client.Stomp` connection and the received :class:`~.StompFrame`. :param ack: Check this option if you wish to automatically ack **MESSAGE** frames after they were handled (successfully or not). :param errorDestination: If a frame was not handled successfully, forward a copy of the offending frame to this destination. Example: ``errorDestination='/queue/back-to-square-one'`` :param onMessageFailed: You can specify a custom error handler which must be a callable with signature :obj:`f(connection, failure, frame, errorDestination)`. Note that a non-trivial choice of this error handler overrides the default behavior (forward frame to error destination and ack it). .. seealso :: The unit tests in the module :mod:`.tests.async_client_integration_test` cover a couple of usage scenarios. """ DEFAULT_ACK_MODE = 'client-individual' def __init__(self, handler, ack=True, errorDestination=None, onMessageFailed=None): if not callable(handler): raise ValueError('Handler is not callable: %s' % handler) self._handler = handler self._ack = ack self._errorDestination = errorDestination self._onMessageFailed = onMessageFailed or sendToErrorDestination self._headers = None self._messages = InFlightOperations('Handler for message') self.log = logging.getLogger(LOG_CATEGORY) @defer.inlineCallbacks def onDisconnect(self, connection, failure, timeout): # @UnusedVariable if not self._messages: defer.returnValue(None) self.log.info( 'Waiting for outstanding message handlers to finish ... [timeout=%s]' % timeout) yield self._waitForMessages(timeout) self.log.info('All handlers complete. Resuming disconnect ...') @defer.inlineCallbacks def onMessage(self, connection, frame, context): """onMessage(connection, frame, context) Handle a message originating from this listener's subscription.""" if context is not self: return with self._messages(frame.headers[StompSpec.MESSAGE_ID_HEADER], self.log) as waiting: try: yield self._handler(connection, frame) except Exception as e: yield self._onMessageFailed(connection, e, frame, self._errorDestination) finally: if self._ack and (self._headers[StompSpec.ACK_HEADER] in StompSpec.CLIENT_ACK_MODES): connection.ack(frame) if not waiting.called: waiting.callback(None) def onSubscribe(self, connection, frame, context): # @UnusedVariable """Set the **ack** header of the **SUBSCRIBE** frame initiating this listener's subscription to the value of the class atrribute :attr:`DEFAULT_ACK_MODE` (if it isn't set already). Keep a copy of the headers for handling messages originating from this subscription.""" if context is not self: return if self._headers is not None: # already subscribed return frame.headers.setdefault(StompSpec.ACK_HEADER, self.DEFAULT_ACK_MODE) self._headers = frame.headers @defer.inlineCallbacks def onUnsubscribe(self, connection, frame, context): # @UnusedVariable """onUnsubscribe(connection, frame, context) Forget everything about this listener's subscription and unregister from the **connection**.""" if context is not self: return yield self._waitForMessages(None) connection.remove(self) def onConnectionLost(self, connection, reason): # @UnusedVariable """onConnectionLost(connection, reason) Forget everything about this listener's subscription and unregister from the **connection**.""" connection.remove(self) def _waitForMessages(self, timeout): return task.cooperate( handler.wait( timeout, StompCancelledError( 'Handlers did not finish in time.')) for handler in self._messages.values()).whenDone()
def __init__(self, timeout=None): self._timeout = timeout self._receipts = InFlightOperations('Waiting for receipt') self.log = logging.getLogger(LOG_CATEGORY)
class SubscriptionListener(Listener): """Corresponds to a STOMP subscription. :param handler: A callable :obj:`f(client, frame)` which accepts a :class:`~.async.client.Stomp` connection and the received :class:`~.StompFrame`. :param ack: Check this option if you wish to automatically ack **MESSAGE** frames after they were handled (successfully or not). :param errorDestination: If a frame was not handled successfully, forward a copy of the offending frame to this destination. Example: ``errorDestination='/queue/back-to-square-one'`` :param onMessageFailed: You can specify a custom error handler which must be a callable with signature :obj:`f(connection, failure, frame, errorDestination)`. Note that a non-trivial choice of this error handler overrides the default behavior (forward frame to error destination and ack it). .. seealso :: The unit tests in the module :mod:`.tests.async_client_integration_test` cover a couple of usage scenarios. """ DEFAULT_ACK_MODE = 'client-individual' def __init__(self, handler, ack=True, errorDestination=None, onMessageFailed=None): if not callable(handler): raise ValueError('Handler is not callable: %s' % handler) self._handler = handler self._ack = ack self._errorDestination = errorDestination self._onMessageFailed = onMessageFailed or sendToErrorDestination self._headers = None self._messages = InFlightOperations('Handler for message') self.log = logging.getLogger(LOG_CATEGORY) @defer.inlineCallbacks def onDisconnect(self, connection, reason, timeout): # @UnusedVariable connection.remove(self) if not self._messages: defer.returnValue(None) self.log.info('Waiting for outstanding message handlers to finish ... [timeout=%s]' % timeout) yield self._waitForMessages(timeout) self.log.info('All handlers complete. Resuming disconnect ...') @defer.inlineCallbacks def onMessage(self, connection, frame, context): """onMessage(connection, frame, context) Handle a message originating from this listener's subscription.""" if context is not self: return with self._messages(frame.headers[StompSpec.MESSAGE_ID_HEADER], self.log) as waiting: try: yield self._handler(connection, frame) except Exception as e: yield self._onMessageFailed(connection, e, frame, self._errorDestination) finally: if self._ack and (self._headers[StompSpec.ACK_HEADER] in StompSpec.CLIENT_ACK_MODES): yield connection.ack(frame) if not waiting.called: waiting.callback(None) def onSubscribe(self, connection, frame, context): # @UnusedVariable """Set the **ack** header of the **SUBSCRIBE** frame initiating this listener's subscription to the value of the class atrribute :attr:`DEFAULT_ACK_MODE` (if it isn't set already). Keep a copy of the headers for handling messages originating from this subscription.""" if context is not self: return if self._headers is not None: # already subscribed return frame.headers.setdefault(StompSpec.ACK_HEADER, self.DEFAULT_ACK_MODE) self._headers = frame.headers @defer.inlineCallbacks def onUnsubscribe(self, connection, frame, context): # @UnusedVariable """onUnsubscribe(connection, frame, context) Forget everything about this listener's subscription and unregister from the **connection**.""" if context is not self: return connection.remove(self) yield self._waitForMessages(None) def onConnectionLost(self, connection, reason): # @UnusedVariable """onConnectionLost(connection, reason) Forget everything about this listener's subscription and unregister from the **connection**.""" connection.remove(self) def _waitForMessages(self, timeout): return task.cooperate(handler.wait(timeout, StompCancelledError('Handlers did not finish in time.')) for handler in list(self._messages.values())).whenDone()
def test_context_single(self): op = InFlightOperations('test') with op(1) as w: self.assertEquals(list(op), [1]) self.assertIsInstance(w, defer.Deferred) self.assertIdentical(w, op[1]) self.assertIdentical(op.get(1), op[1]) self.assertEquals(list(op), []) with op(key=2, log=logging.getLogger(LOG_CATEGORY)): self.assertEquals(list(op), [2]) self.assertIsInstance(op.get(2), defer.Deferred) self.assertIdentical(op.get(2), op[2]) self.assertEquals(list(op), []) try: with op(None, logging.getLogger(LOG_CATEGORY)) as w: reactor.callLater(0, w.cancel) # @UndefinedVariable yield w.wait(timeout=None, fail=None) except CancelledError: pass else: raise self.assertEquals(list(op), []) try: with op(None, logging.getLogger(LOG_CATEGORY)) as w: reactor.callLater( 0, w.errback, StompCancelledError('4711')) # @UndefinedVariable yield w.wait() except StompCancelledError as e: self.assertEquals(str(e), '4711') else: raise self.assertEquals(list(op), []) with op(None, logging.getLogger(LOG_CATEGORY)) as w: reactor.callLater(0, w.callback, 4711) # @UndefinedVariable result = yield w.wait() self.assertEquals(result, 4711) self.assertEquals(list(op), []) try: with op(None) as w: raise RuntimeError('hi') except RuntimeError: pass self.assertEquals(list(op), []) try: yield w except RuntimeError as e: self.assertEquals(str(e), 'hi') else: raise try: with op(None) as w: d = w.wait() raise RuntimeError('hi') except RuntimeError: pass self.assertEquals(list(op), []) try: yield d except RuntimeError as e: self.assertEquals(str(e), 'hi') else: pass